UX: add link to Mapillary, fix #1637
This commit is contained in:
parent
c7089c27a0
commit
804005e402
12 changed files with 82 additions and 37 deletions
|
@ -31,11 +31,12 @@ export default class GenericImageProvider extends ImageProvider {
|
||||||
key: key,
|
key: key,
|
||||||
url: value,
|
url: value,
|
||||||
provider: this,
|
provider: this,
|
||||||
|
id: value
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
SourceIcon(backlinkSource?: string) {
|
SourceIcon() {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,14 @@ import { Utils } from "../../Utils"
|
||||||
export interface ProvidedImage {
|
export interface ProvidedImage {
|
||||||
url: string
|
url: string
|
||||||
key: string
|
key: string
|
||||||
provider: ImageProvider
|
provider: ImageProvider,
|
||||||
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default abstract class ImageProvider {
|
export default abstract class ImageProvider {
|
||||||
public abstract readonly defaultKeyPrefixes: string[]
|
public abstract readonly defaultKeyPrefixes: string[]
|
||||||
|
|
||||||
public abstract SourceIcon(backlinkSource?: string): BaseUIElement
|
public abstract SourceIcon(id?: string, location?: {lon: number, lat: number}): BaseUIElement
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a properies object, maps it onto _all_ the available pictures for this imageProvider
|
* Given a properies object, maps it onto _all_ the available pictures for this imageProvider
|
||||||
|
@ -28,7 +29,7 @@ export default abstract class ImageProvider {
|
||||||
throw "No `defaultKeyPrefixes` defined by this image provider"
|
throw "No `defaultKeyPrefixes` defined by this image provider"
|
||||||
}
|
}
|
||||||
const relevantUrls = new UIEventSource<
|
const relevantUrls = new UIEventSource<
|
||||||
{ url: string; key: string; provider: ImageProvider }[]
|
{ id: string, url: string; key: string; provider: ImageProvider }[]
|
||||||
>([])
|
>([])
|
||||||
const seenValues = new Set<string>()
|
const seenValues = new Set<string>()
|
||||||
allTags.addCallbackAndRunD((tags) => {
|
allTags.addCallbackAndRunD((tags) => {
|
||||||
|
@ -67,4 +68,10 @@ export default abstract class ImageProvider {
|
||||||
public abstract DownloadAttribution(url: string): Promise<LicenseInfo>
|
public abstract DownloadAttribution(url: string): Promise<LicenseInfo>
|
||||||
|
|
||||||
public abstract apiUrls(): string[]
|
public abstract apiUrls(): string[]
|
||||||
|
|
||||||
|
public backlink(): string | undefined {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,7 @@ export class Imgur extends ImageProvider implements ImageUploader {
|
||||||
url: value,
|
url: value,
|
||||||
key: key,
|
key: key,
|
||||||
provider: this,
|
provider: this,
|
||||||
|
id: value
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Svg from "../../Svg"
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
import { LicenseInfo } from "./LicenseInfo"
|
import { LicenseInfo } from "./LicenseInfo"
|
||||||
import Constants from "../../Models/Constants"
|
import Constants from "../../Models/Constants"
|
||||||
|
import Link from "../../UI/Base/Link"
|
||||||
|
|
||||||
export class Mapillary extends ImageProvider {
|
export class Mapillary extends ImageProvider {
|
||||||
public static readonly singleton = new Mapillary()
|
public static readonly singleton = new Mapillary()
|
||||||
|
@ -17,10 +18,6 @@ export class Mapillary extends ImageProvider {
|
||||||
]
|
]
|
||||||
defaultKeyPrefixes = ["mapillary", "image"]
|
defaultKeyPrefixes = ["mapillary", "image"]
|
||||||
|
|
||||||
apiUrls(): string[] {
|
|
||||||
return ["https://mapillary.com", "https://www.mapillary.com", "https://graph.mapillary.com"]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that this is the same URL
|
* Indicates that this is the same URL
|
||||||
* Ignores 'stp' parameter
|
* Ignores 'stp' parameter
|
||||||
|
@ -57,6 +54,22 @@ export class Mapillary extends ImageProvider {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createLink(location: {
|
||||||
|
lon: number,
|
||||||
|
lat: number
|
||||||
|
} = undefined, zoom: number = 17, pKey?: string) {
|
||||||
|
const params = {
|
||||||
|
focus: pKey === undefined ? "map" : "photo",
|
||||||
|
lat: location.lat,
|
||||||
|
lng: location.lon,
|
||||||
|
z: location === undefined ? undefined : Math.max((zoom ?? 2) - 1, 1),
|
||||||
|
pKey,
|
||||||
|
}
|
||||||
|
const baselink = `https://www.mapillary.com/app/?`
|
||||||
|
const paramsStr = Utils.NoNull(Object.keys(params).map(k => params[k] === undefined ? undefined : k + "=" + params[k]))
|
||||||
|
return baselink + paramsStr.join("&")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the correct key for API v4.0
|
* Returns the correct key for API v4.0
|
||||||
*/
|
*/
|
||||||
|
@ -80,8 +93,19 @@ export class Mapillary extends ImageProvider {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
SourceIcon(backlinkSource?: string): BaseUIElement {
|
apiUrls(): string[] {
|
||||||
return Svg.mapillary_svg()
|
return ["https://mapillary.com", "https://www.mapillary.com", "https://graph.mapillary.com"]
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceIcon(id: string, location?: {
|
||||||
|
lon: number,
|
||||||
|
lat: number
|
||||||
|
}): BaseUIElement {
|
||||||
|
const icon = Svg.mapillary_svg()
|
||||||
|
if (!id) {
|
||||||
|
return icon
|
||||||
|
}
|
||||||
|
return new Link(icon, Mapillary.createLink(location, 16, "" + id), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {
|
async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {
|
||||||
|
@ -111,6 +135,7 @@ export class Mapillary extends ImageProvider {
|
||||||
const response = await Utils.downloadJsonCached(metadataUrl, 60 * 60)
|
const response = await Utils.downloadJsonCached(metadataUrl, 60 * 60)
|
||||||
const url = <string>response["thumb_1024_url"]
|
const url = <string>response["thumb_1024_url"]
|
||||||
return {
|
return {
|
||||||
|
id: "" + mapillaryId,
|
||||||
url: url,
|
url: url,
|
||||||
provider: this,
|
provider: this,
|
||||||
key: key,
|
key: key,
|
||||||
|
|
|
@ -15,7 +15,7 @@ export class WikidataImageProvider extends ImageProvider {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceIcon(_?: string): BaseUIElement {
|
public SourceIcon(): BaseUIElement {
|
||||||
return Svg.wikidata_svg()
|
return Svg.wikidata_svg()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import ImageProvider, { ProvidedImage } from "./ImageProvider"
|
import ImageProvider, { ProvidedImage } from "./ImageProvider"
|
||||||
import BaseUIElement from "../../UI/BaseUIElement"
|
import BaseUIElement from "../../UI/BaseUIElement"
|
||||||
import Svg from "../../Svg"
|
import Svg from "../../Svg"
|
||||||
import Link from "../../UI/Base/Link"
|
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
import { LicenseInfo } from "./LicenseInfo"
|
import { LicenseInfo } from "./LicenseInfo"
|
||||||
import Wikimedia from "../Web/Wikimedia"
|
import Wikimedia from "../Web/Wikimedia"
|
||||||
|
@ -70,17 +69,8 @@ export class WikimediaImageProvider extends ImageProvider {
|
||||||
return WikimediaImageProvider.apiUrls
|
return WikimediaImageProvider.apiUrls
|
||||||
}
|
}
|
||||||
|
|
||||||
SourceIcon(backlink: string): BaseUIElement {
|
SourceIcon(): BaseUIElement {
|
||||||
const img = Svg.wikimedia_commons_white_svg().SetStyle("width:2em;height: 2em")
|
return Svg.wikimedia_commons_white_svg().SetStyle("width:2em;height: 2em")
|
||||||
if (backlink === undefined) {
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Link(
|
|
||||||
Svg.wikimedia_commons_white_svg(),
|
|
||||||
`https://commons.wikimedia.org/wiki/${backlink}`,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PrepUrl(value: string): ProvidedImage {
|
public PrepUrl(value: string): ProvidedImage {
|
||||||
|
@ -173,6 +163,6 @@ export class WikimediaImageProvider extends ImageProvider {
|
||||||
if (!image.startsWith("File:")) {
|
if (!image.startsWith("File:")) {
|
||||||
image = "File:" + image
|
image = "File:" + image
|
||||||
}
|
}
|
||||||
return { url: WikimediaImageProvider.PrepareUrl(image), key: undefined, provider: this }
|
return { url: WikimediaImageProvider.PrepareUrl(image), key: undefined, provider: this , id: image}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import Tr from "../Base/Tr.svelte"
|
import Tr from "../Base/Tr.svelte"
|
||||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||||
import Mapillary_black from "../../assets/svg/Mapillary_black.svelte";
|
import Mapillary_black from "../../assets/svg/Mapillary_black.svelte";
|
||||||
|
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
A subtleButton which opens mapillary in a new tab at the current location
|
A subtleButton which opens mapillary in a new tab at the current location
|
||||||
|
@ -16,9 +17,7 @@
|
||||||
}
|
}
|
||||||
let location = mapProperties.location
|
let location = mapProperties.location
|
||||||
let zoom = mapProperties.zoom
|
let zoom = mapProperties.zoom
|
||||||
let mapillaryLink = `https://www.mapillary.com/app/?focus=map&lat=${$location?.lat ?? 0}&lng=${
|
let mapillaryLink = Mapillary.createLink($location, $zoom)
|
||||||
$location?.lon ?? 0
|
|
||||||
}&z=${Math.max(($zoom ?? 2) - 1, 1)}`
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a class="button flex items-center" href={mapillaryLink} target="_blank">
|
<a class="button flex items-center" href={mapillaryLink} target="_blank">
|
||||||
|
|
|
@ -5,21 +5,37 @@ import ImageProvider from "../../Logic/ImageProviders/ImageProvider"
|
||||||
import BaseUIElement from "../BaseUIElement"
|
import BaseUIElement from "../BaseUIElement"
|
||||||
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
|
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
import { Feature } from "geojson"
|
||||||
|
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||||
|
|
||||||
export class AttributedImage extends Combine {
|
export class AttributedImage extends Combine {
|
||||||
constructor(imageInfo: { url: string; provider?: ImageProvider; date?: Date }) {
|
constructor(imageInfo: {
|
||||||
|
id: string,
|
||||||
|
url: string;
|
||||||
|
provider?: ImageProvider;
|
||||||
|
date?: Date
|
||||||
|
}, feature?: Feature) {
|
||||||
let img: BaseUIElement
|
let img: BaseUIElement
|
||||||
img = new Img(imageInfo.url, false, {
|
img = new Img(imageInfo.url, false, {
|
||||||
fallbackImage:
|
fallbackImage:
|
||||||
imageInfo.provider === Mapillary.singleton ? "./assets/svg/blocked.svg" : undefined,
|
imageInfo.provider === Mapillary.singleton ? "./assets/svg/blocked.svg" : undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let location: {
|
||||||
|
lon: number,
|
||||||
|
lat: number
|
||||||
|
} = undefined
|
||||||
|
if (feature) {
|
||||||
|
|
||||||
|
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||||
|
location = { lon, lat }
|
||||||
|
}
|
||||||
let attr: BaseUIElement = undefined
|
let attr: BaseUIElement = undefined
|
||||||
if (imageInfo.provider !== undefined) {
|
if (imageInfo.provider !== undefined) {
|
||||||
attr = new Attribution(
|
attr = new Attribution(
|
||||||
UIEventSource.FromPromise(imageInfo.provider?.DownloadAttribution(imageInfo.url)),
|
UIEventSource.FromPromise(imageInfo.provider?.DownloadAttribution(imageInfo.url)),
|
||||||
imageInfo.provider?.SourceIcon(),
|
imageInfo.provider?.SourceIcon(imageInfo.id, location),
|
||||||
imageInfo.date
|
imageInfo.date,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ export default class Attribution extends VariableUiElement {
|
||||||
title = new Link(title, license.informationLocation.href, true)
|
title = new Link(title, license.informationLocation.href, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Combine([
|
return new Combine([
|
||||||
icon
|
icon
|
||||||
?.SetClass("block left")
|
?.SetClass("block left")
|
||||||
|
|
|
@ -9,19 +9,21 @@ import ImageProvider from "../../Logic/ImageProviders/ImageProvider"
|
||||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||||
import { Changes } from "../../Logic/Osm/Changes"
|
import { Changes } from "../../Logic/Osm/Changes"
|
||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||||
|
import { Feature } from "geojson"
|
||||||
|
|
||||||
export class ImageCarousel extends Toggle {
|
export class ImageCarousel extends Toggle {
|
||||||
constructor(
|
constructor(
|
||||||
images: Store<{ key: string; url: string; provider: ImageProvider }[]>,
|
images: Store<{ id:string, key: string; url: string; provider: ImageProvider }[]>,
|
||||||
tags: Store<any>,
|
tags: Store<any>,
|
||||||
state: { osmConnection?: OsmConnection; changes?: Changes; layout: LayoutConfig }
|
state: { osmConnection?: OsmConnection; changes?: Changes; layout: LayoutConfig },
|
||||||
|
feature: Feature
|
||||||
) {
|
) {
|
||||||
const uiElements = images.map(
|
const uiElements = images.map(
|
||||||
(imageURLS: { key: string; url: string; provider: ImageProvider }[]) => {
|
(imageURLS: { key: string; url: string; provider: ImageProvider, id: string }[]) => {
|
||||||
const uiElements: BaseUIElement[] = []
|
const uiElements: BaseUIElement[] = []
|
||||||
for (const url of imageURLS) {
|
for (const url of imageURLS) {
|
||||||
try {
|
try {
|
||||||
let image = new AttributedImage(url)
|
let image = new AttributedImage(url, feature)
|
||||||
|
|
||||||
if (url.key !== undefined) {
|
if (url.key !== undefined) {
|
||||||
image = new Combine([
|
image = new Combine([
|
||||||
|
|
|
@ -28,11 +28,13 @@
|
||||||
|
|
||||||
const t = Translations.t.image.nearby
|
const t = Translations.t.image.nearby
|
||||||
const c = [lon, lat]
|
const c = [lon, lat]
|
||||||
|
console.log(">>>", image)
|
||||||
let attributedImage = new AttributedImage({
|
let attributedImage = new AttributedImage({
|
||||||
url: image.thumbUrl ?? image.pictureUrl,
|
url: image.thumbUrl ?? image.pictureUrl,
|
||||||
provider: AllImageProviders.byName(image.provider),
|
provider: AllImageProviders.byName(image.provider),
|
||||||
date: new Date(image.date),
|
date: new Date(image.date),
|
||||||
})
|
id: Object.values(image.osmTags)[0]
|
||||||
|
}, feature)
|
||||||
let distance = Math.round(
|
let distance = Math.round(
|
||||||
GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c)
|
GeoOperations.distanceBetween([image.coordinates.lng, image.coordinates.lat], c)
|
||||||
)
|
)
|
||||||
|
|
|
@ -654,7 +654,7 @@ export default class SpecialVisualizations {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
needsUrls: AllImageProviders.apiUrls,
|
needsUrls: AllImageProviders.apiUrls,
|
||||||
constr: (state, tags, args) => {
|
constr: (state, tags, args, feature) => {
|
||||||
let imagePrefixes: string[] = undefined
|
let imagePrefixes: string[] = undefined
|
||||||
if (args.length > 0) {
|
if (args.length > 0) {
|
||||||
imagePrefixes = [].concat(...args.map((a) => a.split(",")))
|
imagePrefixes = [].concat(...args.map((a) => a.split(",")))
|
||||||
|
@ -662,7 +662,8 @@ export default class SpecialVisualizations {
|
||||||
return new ImageCarousel(
|
return new ImageCarousel(
|
||||||
AllImageProviders.LoadImagesFor(tags, imagePrefixes),
|
AllImageProviders.LoadImagesFor(tags, imagePrefixes),
|
||||||
tags,
|
tags,
|
||||||
state
|
state,
|
||||||
|
feature
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue