forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
d959b6b40b
290 changed files with 37178 additions and 2200 deletions
|
|
@ -137,7 +137,6 @@ export default class GeoLocationHandler {
|
|||
}
|
||||
}
|
||||
|
||||
console.trace("Moving the map to the GPS-location")
|
||||
mapLocation.setData({
|
||||
lon: newLocation.longitude,
|
||||
lat: newLocation.latitude,
|
||||
|
|
@ -152,7 +151,6 @@ export default class GeoLocationHandler {
|
|||
private CopyGeolocationIntoMapstate() {
|
||||
const features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
|
||||
this.currentUserLocation = new StaticFeatureSource(features)
|
||||
const keysToCopy = ["speed", "accuracy", "altitude", "altitudeAccuracy", "heading"]
|
||||
let i = 0
|
||||
this.geolocationState.currentGPSLocation.addCallbackAndRun((location) => {
|
||||
if (location === undefined) {
|
||||
|
|
@ -163,18 +161,15 @@ export default class GeoLocationHandler {
|
|||
id: "gps-" + i,
|
||||
"user:location": "yes",
|
||||
date: new Date().toISOString(),
|
||||
// GeolocationObject behaves really weird when indexing, so copying it one by one is the most stable
|
||||
accuracy: location.accuracy,
|
||||
speed: location.speed,
|
||||
altitude: location.altitude,
|
||||
altitudeAccuracy: location.altitudeAccuracy,
|
||||
heading: location.heading,
|
||||
}
|
||||
i++
|
||||
|
||||
for (const k in keysToCopy) {
|
||||
// For some weird reason, the 'Object.keys' method doesn't work for the 'location: GeolocationCoordinates'-object and will thus not copy all the properties when using {...location}
|
||||
// As such, they are copied here
|
||||
if (location[k]) {
|
||||
properties[k] = location[k]
|
||||
}
|
||||
}
|
||||
properties["_all"] = JSON.stringify(location)
|
||||
|
||||
const feature = <Feature>{
|
||||
type: "Feature",
|
||||
properties,
|
||||
|
|
@ -183,7 +178,6 @@ export default class GeoLocationHandler {
|
|||
coordinates: [location.longitude, location.latitude],
|
||||
},
|
||||
}
|
||||
|
||||
features.setData([feature])
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ export class PreferredRasterLayerSelector {
|
|||
this._preferredBackgroundLayer.addCallbackD((_) => self.updateLayer())
|
||||
|
||||
this._availableLayers.addCallbackD((_) => self.updateLayer())
|
||||
self.updateLayer()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -63,6 +64,12 @@ export class PreferredRasterLayerSelector {
|
|||
const foundLayer = isCategory
|
||||
? available.find((l) => l.properties.category === targetLayerId)
|
||||
: available.find((l) => l.properties.id === targetLayerId)
|
||||
console.debug("Updating background layer to", foundLayer?.id, {
|
||||
targetLayerId,
|
||||
queryParam: this._queryParameter?.data,
|
||||
preferred: this._preferredBackgroundLayer?.data,
|
||||
isCategory,
|
||||
})
|
||||
if (foundLayer) {
|
||||
this._rasterLayerSetting.setData(foundLayer)
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -31,11 +31,12 @@ export default class GenericImageProvider extends ImageProvider {
|
|||
key: key,
|
||||
url: value,
|
||||
provider: this,
|
||||
id: value
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
SourceIcon(backlinkSource?: string) {
|
||||
SourceIcon() {
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@ import { Utils } from "../../Utils"
|
|||
export interface ProvidedImage {
|
||||
url: string
|
||||
key: string
|
||||
provider: ImageProvider
|
||||
provider: ImageProvider,
|
||||
id: string
|
||||
}
|
||||
|
||||
export default abstract class ImageProvider {
|
||||
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
|
||||
|
|
@ -28,7 +29,7 @@ export default abstract class ImageProvider {
|
|||
throw "No `defaultKeyPrefixes` defined by this image provider"
|
||||
}
|
||||
const relevantUrls = new UIEventSource<
|
||||
{ url: string; key: string; provider: ImageProvider }[]
|
||||
{ id: string, url: string; key: string; provider: ImageProvider }[]
|
||||
>([])
|
||||
const seenValues = new Set<string>()
|
||||
allTags.addCallbackAndRunD((tags) => {
|
||||
|
|
@ -67,4 +68,10 @@ export default abstract class ImageProvider {
|
|||
public abstract DownloadAttribution(url: string): Promise<LicenseInfo>
|
||||
|
||||
public abstract apiUrls(): string[]
|
||||
|
||||
public backlink(): string | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export class Imgur extends ImageProvider implements ImageUploader {
|
|||
url: value,
|
||||
key: key,
|
||||
provider: this,
|
||||
id: value
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import Svg from "../../Svg"
|
|||
import { Utils } from "../../Utils"
|
||||
import { LicenseInfo } from "./LicenseInfo"
|
||||
import Constants from "../../Models/Constants"
|
||||
import Link from "../../UI/Base/Link"
|
||||
|
||||
export class Mapillary extends ImageProvider {
|
||||
public static readonly singleton = new Mapillary()
|
||||
|
|
@ -17,10 +18,6 @@ export class Mapillary extends ImageProvider {
|
|||
]
|
||||
defaultKeyPrefixes = ["mapillary", "image"]
|
||||
|
||||
apiUrls(): string[] {
|
||||
return ["https://mapillary.com", "https://www.mapillary.com", "https://graph.mapillary.com"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that this is the same URL
|
||||
* Ignores 'stp' parameter
|
||||
|
|
@ -57,6 +54,22 @@ export class Mapillary extends ImageProvider {
|
|||
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
|
||||
*/
|
||||
|
|
@ -80,8 +93,19 @@ export class Mapillary extends ImageProvider {
|
|||
return undefined
|
||||
}
|
||||
|
||||
SourceIcon(backlinkSource?: string): BaseUIElement {
|
||||
return Svg.mapillary_svg()
|
||||
apiUrls(): string[] {
|
||||
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>[]> {
|
||||
|
|
@ -111,6 +135,7 @@ export class Mapillary extends ImageProvider {
|
|||
const response = await Utils.downloadJsonCached(metadataUrl, 60 * 60)
|
||||
const url = <string>response["thumb_1024_url"]
|
||||
return {
|
||||
id: "" + mapillaryId,
|
||||
url: url,
|
||||
provider: this,
|
||||
key: key,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export class WikidataImageProvider extends ImageProvider {
|
|||
super()
|
||||
}
|
||||
|
||||
public SourceIcon(_?: string): BaseUIElement {
|
||||
public SourceIcon(): BaseUIElement {
|
||||
return Svg.wikidata_svg()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import ImageProvider, { ProvidedImage } from "./ImageProvider"
|
||||
import BaseUIElement from "../../UI/BaseUIElement"
|
||||
import Svg from "../../Svg"
|
||||
import Link from "../../UI/Base/Link"
|
||||
import { Utils } from "../../Utils"
|
||||
import { LicenseInfo } from "./LicenseInfo"
|
||||
import Wikimedia from "../Web/Wikimedia"
|
||||
|
|
@ -70,17 +69,8 @@ export class WikimediaImageProvider extends ImageProvider {
|
|||
return WikimediaImageProvider.apiUrls
|
||||
}
|
||||
|
||||
SourceIcon(backlink: string): BaseUIElement {
|
||||
const img = 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
|
||||
)
|
||||
SourceIcon(): BaseUIElement {
|
||||
return Svg.wikimedia_commons_white_svg().SetStyle("width:2em;height: 2em")
|
||||
}
|
||||
|
||||
public PrepUrl(value: string): ProvidedImage {
|
||||
|
|
@ -173,6 +163,6 @@ export class WikimediaImageProvider extends ImageProvider {
|
|||
if (!image.startsWith("File:")) {
|
||||
image = "File:" + image
|
||||
}
|
||||
return { url: WikimediaImageProvider.PrepareUrl(image), key: undefined, provider: this }
|
||||
return { url: WikimediaImageProvider.PrepareUrl(image), key: undefined, provider: this , id: image}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export class Stores {
|
|||
* @param promise
|
||||
* @constructor
|
||||
*/
|
||||
public static FromPromise<T>(promise: Promise<T>): Store<T> {
|
||||
public static FromPromise<T>(promise: Promise<T>): Store<T | undefined> {
|
||||
const src = new UIEventSource<T>(undefined)
|
||||
promise?.then((d) => src.setData(d))
|
||||
promise?.catch((err) => console.warn("Promise failed:", err))
|
||||
|
|
@ -97,7 +97,7 @@ export abstract class Store<T> implements Readable<T> {
|
|||
abstract map<J>(f: (t: T) => J): Store<J>
|
||||
abstract map<J>(f: (t: T) => J, extraStoresToWatch: Store<any>[]): Store<J>
|
||||
|
||||
public mapD<J>(f: (t: T) => J, extraStoresToWatch?: Store<any>[]): Store<J> {
|
||||
public mapD<J>(f: (t: Exclude<T, undefined | null>) => J, extraStoresToWatch?: Store<any>[]): Store<J> {
|
||||
return this.map((t) => {
|
||||
if (t === undefined) {
|
||||
return undefined
|
||||
|
|
@ -105,7 +105,7 @@ export abstract class Store<T> implements Readable<T> {
|
|||
if (t === null) {
|
||||
return null
|
||||
}
|
||||
return f(t)
|
||||
return f(<Exclude<T, undefined | null>> t)
|
||||
}, extraStoresToWatch)
|
||||
}
|
||||
|
||||
|
|
@ -603,7 +603,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
|||
*/
|
||||
public static FromPromiseWithErr<T>(
|
||||
promise: Promise<T>
|
||||
): UIEventSource<{ success: T } | { error: any }> {
|
||||
): UIEventSource<{ success: T } | { error: any } | undefined> {
|
||||
const src = new UIEventSource<{ success: T } | { error: any }>(undefined)
|
||||
promise?.then((d) => src.setData({ success: d }))
|
||||
promise?.catch((err) => src.setData({ error: err }))
|
||||
|
|
@ -771,18 +771,21 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
|||
* Monoidal map which results in a read-only store. 'undefined' is passed 'as is'
|
||||
* Given a function 'f', will construct a new UIEventSource where the contents will always be "f(this.data)'
|
||||
*/
|
||||
public mapD<J>(f: (t: T) => J, extraSources: Store<any>[] = []): Store<J | undefined> {
|
||||
public mapD<J>(f: (t: Exclude<T, undefined | null>) => J, extraSources: Store<any>[] = []): Store<J | undefined> {
|
||||
return new MappedStore(
|
||||
this,
|
||||
(t) => {
|
||||
if (t === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return f(t)
|
||||
if (t === null) {
|
||||
return null
|
||||
}
|
||||
return f(<Exclude<T, undefined | null>> t)
|
||||
},
|
||||
extraSources,
|
||||
this._callbacks,
|
||||
this.data === undefined ? undefined : f(this.data)
|
||||
(this.data === undefined || this.data === null) ?(<undefined | null> this.data) : f(<any> this.data)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,15 +40,26 @@ export interface P4CPicture {
|
|||
export default class NearbyImagesSearch {
|
||||
public static readonly services = ["mapillary", "flickr", "kartaview", "wikicommons"] as const
|
||||
public static readonly apiUrls = ["https://api.flickr.com"]
|
||||
private readonly individualStores: Store<{ images: P4CPicture[]; beforeFilter: number }>[]
|
||||
private readonly individualStores: Store<{ images: P4CPicture[]; beforeFilter: number } | undefined>[]
|
||||
private readonly _store: UIEventSource<P4CPicture[]> = new UIEventSource<P4CPicture[]>([])
|
||||
public readonly store: Store<P4CPicture[]> = this._store
|
||||
public readonly allDone: Store<boolean>
|
||||
private readonly _options: NearbyImageOptions
|
||||
|
||||
constructor(options: NearbyImageOptions, features: IndexedFeatureSource) {
|
||||
this.individualStores = NearbyImagesSearch.services.map((s) =>
|
||||
NearbyImagesSearch.buildPictureFetcher(options, s)
|
||||
)
|
||||
|
||||
const allDone = new UIEventSource(false)
|
||||
this.allDone = allDone
|
||||
const self = this
|
||||
function updateAllDone(){
|
||||
const stillRunning = self.individualStores.some(store => store.data === undefined)
|
||||
allDone.setData(!stillRunning)
|
||||
}
|
||||
self.individualStores.forEach(s => s.addCallback(_ => updateAllDone()))
|
||||
|
||||
this._options = options
|
||||
if (features !== undefined) {
|
||||
const osmImages = new ImagesInLoadedDataFetcher(features).fetchAround({
|
||||
|
|
@ -93,13 +104,17 @@ export default class NearbyImagesSearch {
|
|||
private static buildPictureFetcher(
|
||||
options: NearbyImageOptions,
|
||||
fetcher: P4CService
|
||||
): Store<{ images: P4CPicture[]; beforeFilter: number }> {
|
||||
const p4cStore = Stores.FromPromise<P4CPicture[]>(
|
||||
): Store<{ images: P4CPicture[]; beforeFilter: number } | null | undefined> {
|
||||
const p4cStore = Stores.FromPromiseWithErr<P4CPicture[]>(
|
||||
NearbyImagesSearch.fetchImages(options, fetcher)
|
||||
)
|
||||
const searchRadius = options.searchRadius ?? 100
|
||||
return p4cStore.map(
|
||||
(images) => {
|
||||
return p4cStore.mapD(
|
||||
(imagesState) => {
|
||||
if(imagesState["error"]){
|
||||
return null
|
||||
}
|
||||
let images = imagesState["success"]
|
||||
if (images === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue