forked from MapComplete/MapComplete
chore: automated housekeeping...
This commit is contained in:
parent
79b6927b56
commit
42ded4c1b1
328 changed files with 4062 additions and 1284 deletions
|
@ -8,11 +8,13 @@ import { FeatureSource, WritableFeatureSource } from "../FeatureSource/FeatureSo
|
|||
import { LocalStorageSource } from "../Web/LocalStorageSource"
|
||||
import { GeoOperations } from "../GeoOperations"
|
||||
import { OsmTags } from "../../Models/OsmFeature"
|
||||
import StaticFeatureSource, { WritableStaticFeatureSource } from "../FeatureSource/Sources/StaticFeatureSource"
|
||||
import StaticFeatureSource, {
|
||||
WritableStaticFeatureSource,
|
||||
} from "../FeatureSource/Sources/StaticFeatureSource"
|
||||
import { MapProperties } from "../../Models/MapProperties"
|
||||
import { Orientation } from "../../Sensors/Orientation"
|
||||
|
||||
("use strict")
|
||||
;("use strict")
|
||||
/**
|
||||
* The geolocation-handler takes a map-location and a geolocation state.
|
||||
* It'll move the map as appropriate given the state of the geolocation-API
|
||||
|
@ -29,7 +31,9 @@ export default class GeoLocationHandler {
|
|||
/**
|
||||
* All previously visited points (as 'Point'-objects), with their metadata
|
||||
*/
|
||||
public historicalUserLocations: WritableFeatureSource<Feature<Point, GeoLocationPointProperties>>
|
||||
public historicalUserLocations: WritableFeatureSource<
|
||||
Feature<Point, GeoLocationPointProperties>
|
||||
>
|
||||
|
||||
/**
|
||||
* A featureSource containing a single linestring which has the GPS-history of the user.
|
||||
|
@ -150,7 +154,8 @@ export default class GeoLocationHandler {
|
|||
}
|
||||
|
||||
private CopyGeolocationIntoMapstate() {
|
||||
const features: UIEventSource<Feature<Point, GeoLocationPointProperties>[]> = new UIEventSource<Feature<Point, GeoLocationPointProperties>[]>([])
|
||||
const features: UIEventSource<Feature<Point, GeoLocationPointProperties>[]> =
|
||||
new UIEventSource<Feature<Point, GeoLocationPointProperties>[]>([])
|
||||
this.currentUserLocation = new StaticFeatureSource(features)
|
||||
let i = 0
|
||||
this.geolocationState.currentGPSLocation.addCallbackAndRunD((location) => {
|
||||
|
@ -167,7 +172,7 @@ export default class GeoLocationHandler {
|
|||
altitudeAccuracy: location.altitudeAccuracy,
|
||||
heading: location.heading,
|
||||
alpha: Orientation.singleton.gotMeasurement.data
|
||||
? ("" + Orientation.singleton.alpha.data)
|
||||
? "" + Orientation.singleton.alpha.data
|
||||
: undefined,
|
||||
}
|
||||
i++
|
||||
|
@ -185,7 +190,10 @@ export default class GeoLocationHandler {
|
|||
}
|
||||
|
||||
private initUserLocationTrail() {
|
||||
const features = LocalStorageSource.getParsed<Feature<Point, GeoLocationPointProperties>[]>("gps_location_history", [])
|
||||
const features = LocalStorageSource.getParsed<Feature<Point, GeoLocationPointProperties>[]>(
|
||||
"gps_location_history",
|
||||
[]
|
||||
)
|
||||
const now = new Date().getTime()
|
||||
features.data = features.data.filter((ff) => {
|
||||
if (ff.properties === undefined) {
|
||||
|
@ -198,41 +206,45 @@ export default class GeoLocationHandler {
|
|||
)
|
||||
})
|
||||
features.ping()
|
||||
this.currentUserLocation?.features?.addCallbackAndRunD(([location]: [Feature<Point, GeoLocationPointProperties>]) => {
|
||||
if (location === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const previousLocation = <Feature<Point>>features.data[features.data.length - 1]
|
||||
if (previousLocation !== undefined) {
|
||||
const previousLocationFreshness = new Date(previousLocation.properties.date)
|
||||
const d = GeoOperations.distanceBetween(
|
||||
<[number, number]>previousLocation.geometry.coordinates,
|
||||
<[number, number]>location.geometry.coordinates
|
||||
)
|
||||
let timeDiff = Number.MAX_VALUE // in seconds
|
||||
const olderLocation = features.data[features.data.length - 2]
|
||||
|
||||
if (olderLocation !== undefined) {
|
||||
const olderLocationFreshness = new Date(olderLocation.properties.date)
|
||||
timeDiff =
|
||||
(new Date(previousLocationFreshness).getTime() -
|
||||
new Date(olderLocationFreshness).getTime()) /
|
||||
1000
|
||||
}
|
||||
if (d < 20 && timeDiff < 60) {
|
||||
// Do not append changes less then 20m - it's probably noise anyway
|
||||
this.currentUserLocation?.features?.addCallbackAndRunD(
|
||||
([location]: [Feature<Point, GeoLocationPointProperties>]) => {
|
||||
if (location === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const previousLocation = <Feature<Point>>features.data[features.data.length - 1]
|
||||
if (previousLocation !== undefined) {
|
||||
const previousLocationFreshness = new Date(previousLocation.properties.date)
|
||||
const d = GeoOperations.distanceBetween(
|
||||
<[number, number]>previousLocation.geometry.coordinates,
|
||||
<[number, number]>location.geometry.coordinates
|
||||
)
|
||||
let timeDiff = Number.MAX_VALUE // in seconds
|
||||
const olderLocation = features.data[features.data.length - 2]
|
||||
|
||||
if (olderLocation !== undefined) {
|
||||
const olderLocationFreshness = new Date(olderLocation.properties.date)
|
||||
timeDiff =
|
||||
(new Date(previousLocationFreshness).getTime() -
|
||||
new Date(olderLocationFreshness).getTime()) /
|
||||
1000
|
||||
}
|
||||
if (d < 20 && timeDiff < 60) {
|
||||
// Do not append changes less then 20m - it's probably noise anyway
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const feature = JSON.parse(JSON.stringify(location))
|
||||
feature.properties.id = "gps/" + features.data.length
|
||||
features.data.push(feature)
|
||||
features.ping()
|
||||
}
|
||||
)
|
||||
|
||||
const feature = JSON.parse(JSON.stringify(location))
|
||||
feature.properties.id = "gps/" + features.data.length
|
||||
features.data.push(feature)
|
||||
features.ping()
|
||||
})
|
||||
|
||||
this.historicalUserLocations = new WritableStaticFeatureSource<Feature<Point, GeoLocationPointProperties>>(features)
|
||||
this.historicalUserLocations = new WritableStaticFeatureSource<
|
||||
Feature<Point, GeoLocationPointProperties>
|
||||
>(features)
|
||||
|
||||
const asLine = features.map((allPoints) => {
|
||||
if (allPoints === undefined || allPoints.length < 2) {
|
||||
|
|
|
@ -327,7 +327,11 @@ export class BBox {
|
|||
throw "BBOX has NAN"
|
||||
}
|
||||
if (this.minLat < -90 || this.maxLat > 90) {
|
||||
const msg = "Invalid BBOX detected: latitude is out of range. Did you swap lat & lon somewhere? min:" + this.minLat + "; max:" + this.maxLat
|
||||
const msg =
|
||||
"Invalid BBOX detected: latitude is out of range. Did you swap lat & lon somewhere? min:" +
|
||||
this.minLat +
|
||||
"; max:" +
|
||||
this.maxLat
|
||||
console.trace(msg)
|
||||
throw msg
|
||||
}
|
||||
|
|
|
@ -25,8 +25,7 @@ export default class GeoIndexedStore implements FeatureSource {
|
|||
*/
|
||||
public GetFeaturesWithin(bbox: BBox): Feature[] {
|
||||
const bboxFeature = bbox.asGeojsonCached()
|
||||
return this.features.data.filter((f) =>
|
||||
GeoOperations.completelyWithin(f, bboxFeature))
|
||||
return this.features.data.filter((f) => GeoOperations.completelyWithin(f, bboxFeature))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ export default class ChangeGeometryApplicator implements FeatureSource {
|
|||
|
||||
// Allright! We have a feature to rewrite!
|
||||
const copy = {
|
||||
...feature
|
||||
...feature,
|
||||
}
|
||||
// We only apply the last change as that one'll have the latest geometry
|
||||
const change = changesForFeature[changesForFeature.length - 1]
|
||||
|
|
|
@ -10,12 +10,12 @@ import {
|
|||
MultiPolygon,
|
||||
Point,
|
||||
Polygon,
|
||||
Position
|
||||
Position,
|
||||
} from "geojson"
|
||||
import { Tiles } from "../Models/TileRange"
|
||||
import { Utils } from "../Utils"
|
||||
|
||||
("use strict")
|
||||
;("use strict")
|
||||
|
||||
export class GeoOperations {
|
||||
private static readonly _earthRadius: number = 6378137
|
||||
|
|
|
@ -132,7 +132,7 @@ export default class AllImageProviders {
|
|||
const singleSource = tags.bindD((tags) => imageProvider.getRelevantUrls(tags, prefixes))
|
||||
allSources.push(singleSource)
|
||||
}
|
||||
const source = Stores.fromStoresArray(allSources).map(result => {
|
||||
const source = Stores.fromStoresArray(allSources).map((result) => {
|
||||
const all = [].concat(...result)
|
||||
return Utils.DedupOnId(all, (i) => i?.id ?? i?.url)
|
||||
})
|
||||
|
|
|
@ -24,12 +24,12 @@ export interface ProvidedImage {
|
|||
}
|
||||
|
||||
export interface PanoramaView {
|
||||
url: string,
|
||||
url: string
|
||||
/**
|
||||
* 0 - 359
|
||||
* Degrees in which the picture is taken, with north = 0; going clockwise
|
||||
*/
|
||||
northOffset?: number,
|
||||
northOffset?: number
|
||||
pitchOffset?: number
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,6 @@ export interface HotspotProperties {
|
|||
pitch: number | "auto"
|
||||
|
||||
gotoPanorama: Feature<Point, PanoramaView>
|
||||
|
||||
}
|
||||
|
||||
export default abstract class ImageProvider {
|
||||
|
@ -125,7 +124,9 @@ export default abstract class ImageProvider {
|
|||
|
||||
public abstract apiUrls(): string[]
|
||||
|
||||
public abstract getPanoramaInfo(image: { id: string }): Promise<Feature<Point, PanoramaView>> | undefined;
|
||||
public abstract getPanoramaInfo(image: {
|
||||
id: string
|
||||
}): Promise<Feature<Point, PanoramaView>> | undefined
|
||||
|
||||
public static async offerImageAsDownload(image: ProvidedImage) {
|
||||
const response = await fetch(image.url_hd ?? image.url)
|
||||
|
@ -134,5 +135,4 @@ export default abstract class ImageProvider {
|
|||
mimetype: "image/jpg",
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,12 +36,16 @@ export class ImageUploadManager {
|
|||
* Keeps track of the _features_ for which an upload failed. Only used to give an indication to the user.
|
||||
* Every time an image upload fails, the featureID is added to the list. Not persisted (and should not be)
|
||||
*/
|
||||
private readonly _fails: UIEventSource<ImageUploadArguments[]> = new UIEventSource<ImageUploadArguments[]>([])
|
||||
public readonly fails: Store<string[]> = this._fails.map(args => args.map(a => a.featureId))
|
||||
private readonly _fails: UIEventSource<ImageUploadArguments[]> = new UIEventSource<
|
||||
ImageUploadArguments[]
|
||||
>([])
|
||||
public readonly fails: Store<string[]> = this._fails.map((args) => args.map((a) => a.featureId))
|
||||
/**
|
||||
* FeatureIDs of queued items
|
||||
*/
|
||||
public readonly queued: Store<string[]> = this._queue.imagesInQueue.map(queue => queue.map(q => q.featureId))
|
||||
public readonly queued: Store<string[]> = this._queue.imagesInQueue.map((queue) =>
|
||||
queue.map((q) => q.featureId)
|
||||
)
|
||||
public readonly queuedArgs = this._queue.imagesInQueue
|
||||
/**
|
||||
* The feature for which an upload is currently running
|
||||
|
@ -79,7 +83,7 @@ export class ImageUploadManager {
|
|||
if (sizeInBytes > this._uploader.maxFileSizeInMegabytes * 1000000) {
|
||||
const error = Translations.t.image.toBig.Subs({
|
||||
actual_size: Math.floor(sizeInBytes / 1000000) + "MB",
|
||||
max_size: this._uploader.maxFileSizeInMegabytes + "MB"
|
||||
max_size: this._uploader.maxFileSizeInMegabytes + "MB",
|
||||
})
|
||||
return { error }
|
||||
}
|
||||
|
@ -118,7 +122,6 @@ export class ImageUploadManager {
|
|||
const tags: OsmTags = tagsStore.data
|
||||
const featureId = <OsmId | NoteId>tags.id
|
||||
|
||||
|
||||
const author = this._osmConnection?.userDetails?.data?.name ?? "Anonymous" // Might be a note upload
|
||||
|
||||
/**
|
||||
|
@ -134,13 +137,16 @@ export class ImageUploadManager {
|
|||
location,
|
||||
date: new Date().getTime(),
|
||||
layoutId: this._theme.id,
|
||||
author, blob: file, featureId, noblur, targetKey
|
||||
author,
|
||||
blob: file,
|
||||
featureId,
|
||||
noblur,
|
||||
targetKey,
|
||||
}
|
||||
console.log("Args are", args)
|
||||
|
||||
this._queue.add(args)
|
||||
this.uploadQueue()
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -201,23 +207,29 @@ export class ImageUploadManager {
|
|||
this._fails.ping()
|
||||
return
|
||||
}
|
||||
this._fails.set(this._fails.data.filter(a => a !== args))
|
||||
let properties: UIEventSource<Record<string, string>> = this._featureProperties.getStore(args.featureId)
|
||||
this._fails.set(this._fails.data.filter((a) => a !== args))
|
||||
let properties: UIEventSource<Record<string, string>> = this._featureProperties.getStore(
|
||||
args.featureId
|
||||
)
|
||||
|
||||
if (args.featureId.startsWith("note/")) {
|
||||
// This is an OSM-note
|
||||
const url = result.absoluteUrl
|
||||
await this._osmConnection.addCommentToNote(args.featureId, url)
|
||||
const properties: UIEventSource<Record<string, string>> = this._featureProperties.getStore(args.featureId)
|
||||
const properties: UIEventSource<Record<string, string>> =
|
||||
this._featureProperties.getStore(args.featureId)
|
||||
if (properties) {
|
||||
// Properties will not be defined if the note isn't loaded, but that is no problem as the below code is only relevant if the note is shown
|
||||
NoteCommentElement.addCommentTo(url, properties, {
|
||||
osmConnection: this._osmConnection
|
||||
osmConnection: this._osmConnection,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (properties === undefined) {
|
||||
const downloaded = await new OsmObjectDownloader(this._osmConnection.Backend(), this._changes).DownloadObjectAsync(args.featureId)
|
||||
const downloaded = await new OsmObjectDownloader(
|
||||
this._osmConnection.Backend(),
|
||||
this._changes
|
||||
).DownloadObjectAsync(args.featureId)
|
||||
if (downloaded === "deleted") {
|
||||
this._queue.delete(args)
|
||||
return
|
||||
|
@ -232,7 +244,7 @@ export class ImageUploadManager {
|
|||
properties,
|
||||
{
|
||||
theme: properties?.data?.["_orig_theme"] ?? this._theme.id,
|
||||
changeType: "add-image"
|
||||
changeType: "add-image",
|
||||
}
|
||||
)
|
||||
await this._changes.applyAction(action)
|
||||
|
@ -240,7 +252,6 @@ export class ImageUploadManager {
|
|||
}
|
||||
|
||||
this._queue.delete(args)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -259,23 +270,15 @@ export class ImageUploadManager {
|
|||
* @private
|
||||
*/
|
||||
private async attemptSingleUpload(
|
||||
{
|
||||
featureId,
|
||||
author,
|
||||
blob,
|
||||
targetKey,
|
||||
noblur,
|
||||
location
|
||||
}: ImageUploadArguments,
|
||||
{ featureId, author, blob, targetKey, noblur, location }: ImageUploadArguments,
|
||||
reportOnFail: boolean
|
||||
): Promise<UploadResult | undefined> {
|
||||
|
||||
let key: string
|
||||
let value: string
|
||||
let absoluteUrl: string
|
||||
|
||||
try {
|
||||
({ key, value, absoluteUrl } = await this._uploader.uploadImage(
|
||||
;({ key, value, absoluteUrl } = await this._uploader.uploadImage(
|
||||
blob,
|
||||
location,
|
||||
author,
|
||||
|
@ -284,14 +287,13 @@ export class ImageUploadManager {
|
|||
} catch (e) {
|
||||
console.error("Could again not upload image due to", e)
|
||||
if (reportOnFail) {
|
||||
|
||||
await this._reportError(
|
||||
e,
|
||||
JSON.stringify({
|
||||
ctx: "While uploading an image in the Image Upload Manager",
|
||||
featureId,
|
||||
author,
|
||||
targetKey
|
||||
targetKey,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -304,5 +306,4 @@ export class ImageUploadManager {
|
|||
}
|
||||
return { key, absoluteUrl, value }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@ import { IdbLocalStorage } from "../Web/IdbLocalStorage"
|
|||
import { Store, UIEventSource } from "../UIEventSource"
|
||||
|
||||
export interface ImageUploadArguments {
|
||||
featureId: string,
|
||||
readonly author: string,
|
||||
readonly blob: File,
|
||||
readonly targetKey: string | undefined,
|
||||
readonly noblur: boolean,
|
||||
readonly location: [number, number],
|
||||
featureId: string
|
||||
readonly author: string
|
||||
readonly blob: File
|
||||
readonly targetKey: string | undefined
|
||||
readonly noblur: boolean
|
||||
readonly location: [number, number]
|
||||
readonly layoutId: string
|
||||
readonly date: number
|
||||
}
|
||||
|
@ -17,14 +17,15 @@ export interface ImageUploadArguments {
|
|||
* It is backed up in the indexedDB as to not drop images in case of connection problems
|
||||
*/
|
||||
export default class ImageUploadQueue {
|
||||
|
||||
public static readonly singleton = new ImageUploadQueue()
|
||||
private readonly _imagesInQueue: UIEventSource<ImageUploadArguments[]>
|
||||
|
||||
public readonly imagesInQueue: Store<ImageUploadArguments[]>
|
||||
|
||||
private constructor() {
|
||||
this._imagesInQueue = IdbLocalStorage.Get<ImageUploadArguments[]>("failed-images-backup", { defaultValue: [] })
|
||||
this._imagesInQueue = IdbLocalStorage.Get<ImageUploadArguments[]>("failed-images-backup", {
|
||||
defaultValue: [],
|
||||
})
|
||||
this.imagesInQueue = this._imagesInQueue
|
||||
}
|
||||
|
||||
|
@ -44,7 +45,6 @@ export default class ImageUploadQueue {
|
|||
}
|
||||
|
||||
applyRemapping(oldId: string, newId: string) {
|
||||
|
||||
let hasChange = false
|
||||
for (const img of this._imagesInQueue.data) {
|
||||
if (img.featureId === oldId) {
|
||||
|
|
|
@ -32,7 +32,7 @@ export class Imgur extends ImageProvider {
|
|||
key: key,
|
||||
provider: this,
|
||||
id: value,
|
||||
isSpherical: false
|
||||
isSpherical: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export class Mapillary extends ImageProvider {
|
|||
"http://mapillary.com",
|
||||
"https://mapillary.com",
|
||||
"http://www.mapillary.com",
|
||||
"https://www.mapillary.com"
|
||||
"https://www.mapillary.com",
|
||||
]
|
||||
defaultKeyPrefixes = ["mapillary", "image"]
|
||||
|
||||
|
@ -70,7 +70,7 @@ export class Mapillary extends ImageProvider {
|
|||
lat: location?.lat,
|
||||
lng: location?.lon,
|
||||
z: location === undefined ? undefined : Math.max((zoom ?? 2) - 1, 1),
|
||||
pKey
|
||||
pKey,
|
||||
}
|
||||
const baselink = `https://www.mapillary.com/app/?`
|
||||
const paramsStr = Utils.NoNull(
|
||||
|
@ -140,41 +140,39 @@ export class Mapillary extends ImageProvider {
|
|||
return [img]
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Download data necessary for the 360°-viewer
|
||||
* @param pkey
|
||||
* @constructor
|
||||
*/
|
||||
public async getPanoramaInfo(image: { id: number | string }): Promise<Feature<Point, PanoramaView>> {
|
||||
public async getPanoramaInfo(image: {
|
||||
id: number | string
|
||||
}): Promise<Feature<Point, PanoramaView>> {
|
||||
const pkey = image.id
|
||||
const metadataUrl =
|
||||
"https://graph.mapillary.com/" +
|
||||
pkey +
|
||||
"?fields=computed_compass_angle,geometry,is_pano,thumb_2048_url,thumb_original_url&access_token=" +
|
||||
Constants.mapillary_client_token_v4
|
||||
const response = await Utils.downloadJsonCached<
|
||||
{
|
||||
computed_compass_angle: number,
|
||||
geometry: Point,
|
||||
const response = await Utils.downloadJsonCached<{
|
||||
computed_compass_angle: number
|
||||
geometry: Point
|
||||
|
||||
is_pano: boolean,
|
||||
thumb_2048_url: string,
|
||||
thumb_original_url: string,
|
||||
id: string,
|
||||
|
||||
}>(metadataUrl, 60 * 60)
|
||||
is_pano: boolean
|
||||
thumb_2048_url: string
|
||||
thumb_original_url: string
|
||||
id: string
|
||||
}>(metadataUrl, 60 * 60)
|
||||
return {
|
||||
type: "Feature",
|
||||
geometry: response.geometry,
|
||||
properties: {
|
||||
url: response.thumb_2048_url,
|
||||
northOffset: response.computed_compass_angle
|
||||
}
|
||||
northOffset: response.computed_compass_angle,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async DownloadAttribution(providedImage: { id: string }): Promise<LicenseInfo> {
|
||||
const mapillaryId = providedImage.id
|
||||
const metadataUrl =
|
||||
|
@ -183,7 +181,10 @@ export class Mapillary extends ImageProvider {
|
|||
"?fields=thumb_1024_url,thumb_original_url,captured_at,creator&access_token=" +
|
||||
Constants.mapillary_client_token_v4
|
||||
const response = await Utils.downloadJsonCached<{
|
||||
thumb_1024_url: string, thumb_original_url: string, captured_at, creator: string
|
||||
thumb_1024_url: string
|
||||
thumb_original_url: string
|
||||
captured_at
|
||||
creator: string
|
||||
}>(metadataUrl, 60 * 60)
|
||||
|
||||
const license = new LicenseInfo()
|
||||
|
@ -207,13 +208,13 @@ export class Mapillary extends ImageProvider {
|
|||
"?fields=thumb_1024_url,thumb_original_url,captured_at,compass_angle,geometry,computed_geometry,creator,camera_type&access_token=" +
|
||||
Constants.mapillary_client_token_v4
|
||||
const response = await Utils.downloadJsonCached<{
|
||||
thumb_1024_url: string,
|
||||
thumb_original_url: string,
|
||||
captured_at,
|
||||
compass_angle: number,
|
||||
creator: string,
|
||||
computed_geometry: Point,
|
||||
geometry: Point,
|
||||
thumb_1024_url: string
|
||||
thumb_original_url: string
|
||||
captured_at
|
||||
compass_angle: number
|
||||
creator: string
|
||||
computed_geometry: Point
|
||||
geometry: Point
|
||||
camera_type: "equirectangular" | "spherical" | string
|
||||
}>(metadataUrl, 60 * 60)
|
||||
const url = <string>response["thumb_1024_url"]
|
||||
|
@ -230,9 +231,10 @@ export class Mapillary extends ImageProvider {
|
|||
date,
|
||||
key,
|
||||
rotation,
|
||||
isSpherical: response.camera_type === "spherical" || response.camera_type === "equirectangular",
|
||||
isSpherical:
|
||||
response.camera_type === "spherical" || response.camera_type === "equirectangular",
|
||||
lat: geometry.coordinates[1],
|
||||
lon: geometry.coordinates[0]
|
||||
lon: geometry.coordinates[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,7 +191,9 @@ export default class PanoramaxImageProvider extends ImageProvider {
|
|||
return new Panoramax(host)
|
||||
}
|
||||
|
||||
public async getPanoramaInfo(image: { id: string }): Promise<Feature<Point, PanoramaView>> | undefined {
|
||||
public async getPanoramaInfo(image: {
|
||||
id: string
|
||||
}): Promise<Feature<Point, PanoramaView>> | undefined {
|
||||
const imageInfo = await PanoramaxImageProvider.xyz.imageInfo(image.id)
|
||||
const url = (imageInfo.assets.sd ?? imageInfo.assets.thumb ?? imageInfo.assets.hd).href
|
||||
const northOffset = imageInfo.properties["view:azimuth"]
|
||||
|
@ -200,8 +202,10 @@ export default class PanoramaxImageProvider extends ImageProvider {
|
|||
type: "Feature",
|
||||
geometry: imageInfo.geometry,
|
||||
properties: {
|
||||
url, northOffset, pitchOffset
|
||||
}
|
||||
url,
|
||||
northOffset,
|
||||
pitchOffset,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,12 @@ import { Utils } from "../../Utils"
|
|||
import { Feature, Point } from "geojson"
|
||||
|
||||
export class WikidataImageProvider extends ImageProvider {
|
||||
|
||||
|
||||
public static readonly singleton = new WikidataImageProvider()
|
||||
public readonly defaultKeyPrefixes = ["wikidata"]
|
||||
public readonly name = "Wikidata"
|
||||
private static readonly keyBlacklist: ReadonlySet<string> = new Set([
|
||||
"mapillary",
|
||||
...Utils.Times((i) => "mapillary:" + i, 10)
|
||||
...Utils.Times((i) => "mapillary:" + i, 10),
|
||||
])
|
||||
|
||||
private constructor() {
|
||||
|
|
|
@ -189,7 +189,7 @@ export class WikimediaImageProvider extends ImageProvider {
|
|||
key: undefined,
|
||||
provider: this,
|
||||
id: image,
|
||||
isSpherical: false
|
||||
isSpherical: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,9 @@ export class Changes {
|
|||
public readonly backend: string
|
||||
public readonly isUploading = new UIEventSource(false)
|
||||
public readonly errors = new UIEventSource<string[]>([], "upload-errors")
|
||||
private readonly historicalUserLocations?: FeatureSource<Feature<Point, GeoLocationPointProperties>>
|
||||
private readonly historicalUserLocations?: FeatureSource<
|
||||
Feature<Point, GeoLocationPointProperties>
|
||||
>
|
||||
private _nextId: number = 0 // Newly assigned ID's are negative
|
||||
private readonly previouslyCreated: OsmObject[] = []
|
||||
private readonly _leftRightSensitive: boolean
|
||||
|
@ -67,7 +69,7 @@ export class Changes {
|
|||
if (isNaN(this._nextId) && state.reportError !== undefined) {
|
||||
state.reportError(
|
||||
"Got a NaN as nextID. Pending changes IDs are:" +
|
||||
this.pendingChanges.data?.map((pch) => pch?.id).join(".")
|
||||
this.pendingChanges.data?.map((pch) => pch?.id).join(".")
|
||||
)
|
||||
this._nextId = -100
|
||||
}
|
||||
|
@ -91,19 +93,22 @@ export class Changes {
|
|||
return new Changes({
|
||||
osmConnection: new OsmConnection(),
|
||||
featureSwitches: {
|
||||
featureSwitchIsTesting: new ImmutableStore(true)
|
||||
}
|
||||
featureSwitchIsTesting: new ImmutableStore(true),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
public static async createChangesetXMLForJosm(actions: OsmChangeAction[], osmConnection?: OsmConnection): Promise<string> {
|
||||
public static async createChangesetXMLForJosm(
|
||||
actions: OsmChangeAction[],
|
||||
osmConnection?: OsmConnection
|
||||
): Promise<string> {
|
||||
osmConnection ??= new OsmConnection()
|
||||
const changes = new Changes({
|
||||
osmConnection
|
||||
osmConnection,
|
||||
})
|
||||
const descriptions: ChangeDescription[] = []
|
||||
for (const action of actions) {
|
||||
descriptions.push(...await action.Perform(changes))
|
||||
descriptions.push(...(await action.Perform(changes)))
|
||||
}
|
||||
const downloader = new OsmObjectDownloader(osmConnection.Backend(), undefined)
|
||||
const downloaded: OsmObject[] = []
|
||||
|
@ -114,7 +119,10 @@ export class Changes {
|
|||
}
|
||||
downloaded.push(osmObj)
|
||||
}
|
||||
return Changes.buildChangesetXML("", changes.CreateChangesetObjects(descriptions, downloaded))
|
||||
return Changes.buildChangesetXML(
|
||||
"",
|
||||
changes.CreateChangesetObjects(descriptions, downloaded)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -179,50 +187,50 @@ export class Changes {
|
|||
[
|
||||
{
|
||||
key: "comment",
|
||||
docs: "The changeset comment. Will be a fixed string, mentioning the theme"
|
||||
docs: "The changeset comment. Will be a fixed string, mentioning the theme",
|
||||
},
|
||||
{
|
||||
key: "theme",
|
||||
docs: "The name of the theme that was used to create this change. "
|
||||
docs: "The name of the theme that was used to create this change. ",
|
||||
},
|
||||
{
|
||||
key: "source",
|
||||
value: "survey",
|
||||
docs: "The contributor had their geolocation enabled while making changes"
|
||||
docs: "The contributor had their geolocation enabled while making changes",
|
||||
},
|
||||
{
|
||||
key: "change_within_{distance}",
|
||||
docs: "If the contributor enabled their geolocation, this will hint how far away they were from the objects they edited. This gives an indication of proximity and if they truly surveyed or were armchair-mapping"
|
||||
docs: "If the contributor enabled their geolocation, this will hint how far away they were from the objects they edited. This gives an indication of proximity and if they truly surveyed or were armchair-mapping",
|
||||
},
|
||||
{
|
||||
key: "change_over_{distance}",
|
||||
docs: "If the contributor enabled their geolocation, this will hint how far away they were from the objects they edited. If they were over 5000m away, the might have been armchair-mapping"
|
||||
docs: "If the contributor enabled their geolocation, this will hint how far away they were from the objects they edited. If they were over 5000m away, the might have been armchair-mapping",
|
||||
},
|
||||
{
|
||||
key: "created_by",
|
||||
value: "MapComplete <version>",
|
||||
docs: "The piece of software used to create this changeset; will always start with MapComplete, followed by the version number"
|
||||
docs: "The piece of software used to create this changeset; will always start with MapComplete, followed by the version number",
|
||||
},
|
||||
{
|
||||
key: "locale",
|
||||
value: "en|nl|de|...",
|
||||
docs: "The code of the language that the contributor used MapComplete in. Hints what language the user speaks."
|
||||
docs: "The code of the language that the contributor used MapComplete in. Hints what language the user speaks.",
|
||||
},
|
||||
{
|
||||
key: "host",
|
||||
value: "https://mapcomplete.org/<theme>",
|
||||
docs: "The URL that the contributor used to make changes. One can see the used instance with this"
|
||||
docs: "The URL that the contributor used to make changes. One can see the used instance with this",
|
||||
},
|
||||
{
|
||||
key: "imagery",
|
||||
docs: "The identifier of the used background layer, this will probably be an identifier from the [editor layer index](https://github.com/osmlab/editor-layer-index)"
|
||||
}
|
||||
docs: "The identifier of the used background layer, this will probably be an identifier from the [editor layer index](https://github.com/osmlab/editor-layer-index)",
|
||||
},
|
||||
],
|
||||
"default"
|
||||
),
|
||||
...addSource(ChangeTagAction.metatags, "ChangeTag"),
|
||||
...addSource(ChangeLocationAction.metatags, "ChangeLocation"),
|
||||
...addSource(DeleteAction.metatags, "DeleteAction")
|
||||
...addSource(DeleteAction.metatags, "DeleteAction"),
|
||||
// TODO
|
||||
/*
|
||||
...DeleteAction.metatags,
|
||||
|
@ -244,11 +252,11 @@ export class Changes {
|
|||
docs,
|
||||
specialMotivation
|
||||
? "This might give a reason per modified node or way"
|
||||
: ""
|
||||
: "",
|
||||
].join("\n"),
|
||||
source
|
||||
source,
|
||||
])
|
||||
)
|
||||
),
|
||||
].join("\n\n")
|
||||
}
|
||||
|
||||
|
@ -267,7 +275,7 @@ export class Changes {
|
|||
this._changesetHandler._remappings.has("node/" + this._nextId) ||
|
||||
this._changesetHandler._remappings.has("way/" + this._nextId) ||
|
||||
this._changesetHandler._remappings.has("relation/" + this._nextId)
|
||||
)
|
||||
)
|
||||
return this._nextId
|
||||
}
|
||||
|
||||
|
@ -504,7 +512,7 @@ export class Changes {
|
|||
const result = {
|
||||
newObjects: [],
|
||||
modifiedObjects: [],
|
||||
deletedObjects: []
|
||||
deletedObjects: [],
|
||||
}
|
||||
|
||||
objects.forEach((v, id) => {
|
||||
|
@ -665,7 +673,7 @@ export class Changes {
|
|||
} else {
|
||||
this._reportError(
|
||||
`Got an orphaned change. The 'creation'-change description for ${c.type}/${c.id} got lost. Permanently dropping this change:` +
|
||||
JSON.stringify(c)
|
||||
JSON.stringify(c)
|
||||
)
|
||||
}
|
||||
return
|
||||
|
@ -676,10 +684,10 @@ export class Changes {
|
|||
} else {
|
||||
console.log(
|
||||
"Refusing change about " +
|
||||
c.type +
|
||||
"/" +
|
||||
c.id +
|
||||
" as not in the objects. No internet?"
|
||||
c.type +
|
||||
"/" +
|
||||
c.id +
|
||||
" as not in the objects. No internet?"
|
||||
)
|
||||
refused.push(c)
|
||||
}
|
||||
|
@ -694,7 +702,7 @@ export class Changes {
|
|||
*/
|
||||
private async flushSelectChanges(
|
||||
pending: ChangeDescription[],
|
||||
openChangeset: UIEventSource<{ id: number, opened: number }>
|
||||
openChangeset: UIEventSource<{ id: number; opened: number }>
|
||||
): Promise<ChangeDescription[]> {
|
||||
const neededIds = Changes.GetNeededIds(pending)
|
||||
/* Download the latest version of the OSM-objects
|
||||
|
@ -775,14 +783,14 @@ export class Changes {
|
|||
([key, count]) => ({
|
||||
key: key,
|
||||
value: count,
|
||||
aggregate: true
|
||||
aggregate: true,
|
||||
})
|
||||
)
|
||||
const motivations = pending
|
||||
.filter((descr) => descr.meta.specialMotivation !== undefined)
|
||||
.map((descr) => ({
|
||||
key: descr.meta.changeType + ":" + descr.type + "/" + descr.id,
|
||||
value: descr.meta.specialMotivation
|
||||
value: descr.meta.specialMotivation,
|
||||
}))
|
||||
|
||||
const distances = Utils.NoNull(pending.map((descr) => descr.meta.distanceToObject))
|
||||
|
@ -813,7 +821,7 @@ export class Changes {
|
|||
return {
|
||||
key,
|
||||
value: count,
|
||||
aggregate: true
|
||||
aggregate: true,
|
||||
}
|
||||
})
|
||||
)
|
||||
|
@ -828,20 +836,19 @@ export class Changes {
|
|||
const metatags: ChangesetTag[] = [
|
||||
{
|
||||
key: "comment",
|
||||
value: comment
|
||||
value: comment,
|
||||
},
|
||||
{
|
||||
key: "theme",
|
||||
value: theme
|
||||
value: theme,
|
||||
},
|
||||
...perType,
|
||||
...motivations,
|
||||
...perBinMessage
|
||||
...perBinMessage,
|
||||
]
|
||||
return metatags
|
||||
}
|
||||
|
||||
|
||||
private async flushChangesAsync(): Promise<void> {
|
||||
try {
|
||||
// At last, we build the changeset and upload
|
||||
|
@ -862,9 +869,9 @@ export class Changes {
|
|||
const openChangeset = this.state.osmConnection.getCurrentChangesetFor(theme)
|
||||
console.log(
|
||||
"Using current-open-changeset-" +
|
||||
theme +
|
||||
" from the preferences, got " +
|
||||
openChangeset.data
|
||||
theme +
|
||||
" from the preferences, got " +
|
||||
openChangeset.data
|
||||
)
|
||||
|
||||
const refused = await this.flushSelectChanges(pendingChanges, openChangeset)
|
||||
|
|
|
@ -114,7 +114,7 @@ export class ChangesetHandler {
|
|||
|
||||
private async UploadWithNew(
|
||||
generateChangeXML: (csid: number, remappings: Map<string, string>) => string,
|
||||
openChangeset: UIEventSource<{ id: number, opened: number }>,
|
||||
openChangeset: UIEventSource<{ id: number; opened: number }>,
|
||||
extraMetaTags: ChangesetTag[]
|
||||
) {
|
||||
const csId = await this.OpenChangeset(extraMetaTags)
|
||||
|
@ -146,7 +146,7 @@ export class ChangesetHandler {
|
|||
public async UploadChangeset(
|
||||
generateChangeXML: (csid: number, remappings: Map<string, string>) => string,
|
||||
extraMetaTags: ChangesetTag[],
|
||||
openChangeset: UIEventSource<{ id: number, opened: number }>
|
||||
openChangeset: UIEventSource<{ id: number; opened: number }>
|
||||
): Promise<void> {
|
||||
if (
|
||||
!extraMetaTags.some((tag) => tag.key === "comment") ||
|
||||
|
@ -171,8 +171,9 @@ export class ChangesetHandler {
|
|||
|
||||
console.log("Trying to reuse changeset", openChangeset.data)
|
||||
const now = new Date()
|
||||
const changesetIsUsable = openChangeset.data !== undefined &&
|
||||
(now.getTime() - openChangeset.data.opened < 24 * 60 * 60 * 1000)
|
||||
const changesetIsUsable =
|
||||
openChangeset.data !== undefined &&
|
||||
now.getTime() - openChangeset.data.opened < 24 * 60 * 60 * 1000
|
||||
if (changesetIsUsable) {
|
||||
try {
|
||||
const csId = openChangeset.data
|
||||
|
|
|
@ -246,13 +246,20 @@ export class OsmConnection {
|
|||
}
|
||||
|
||||
public getPreference<T extends string = string>(
|
||||
key: string, options?: {
|
||||
defaultValue?: string,
|
||||
prefix?: "mapcomplete-" | string,
|
||||
key: string,
|
||||
options?: {
|
||||
defaultValue?: string
|
||||
prefix?: "mapcomplete-" | string
|
||||
saveToLocalStorage?: true | boolean
|
||||
}
|
||||
): UIEventSource<T | undefined> {
|
||||
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, options?.defaultValue, options?.prefix ?? "mapcomplete-")
|
||||
return <UIEventSource<T>>(
|
||||
this.preferencesHandler.getPreference(
|
||||
key,
|
||||
options?.defaultValue,
|
||||
options?.prefix ?? "mapcomplete-"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
public LogOut() {
|
||||
|
@ -735,10 +742,8 @@ export class OsmConnection {
|
|||
}
|
||||
|
||||
public getCurrentChangesetFor(theme: string) {
|
||||
return UIEventSource.asObject<{ id: number, opened: number }>(
|
||||
this.GetPreference(
|
||||
"current-changeset-" + theme
|
||||
),
|
||||
return UIEventSource.asObject<{ id: number; opened: number }>(
|
||||
this.GetPreference("current-changeset-" + theme),
|
||||
undefined
|
||||
)
|
||||
}
|
||||
|
@ -748,9 +753,10 @@ export class OsmConnection {
|
|||
*/
|
||||
public getAllOpenChangesetsPreferences(): Store<string[]> {
|
||||
const prefix = "current-changeset-"
|
||||
return this.preferencesHandler.allPreferences.map(dict =>
|
||||
return this.preferencesHandler.allPreferences.map((dict) =>
|
||||
Object.keys(dict)
|
||||
.filter(k => k.startsWith(prefix))
|
||||
.map(k => k.substring(prefix.length)))
|
||||
.filter((k) => k.startsWith(prefix))
|
||||
.map((k) => k.substring(prefix.length))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,8 +91,16 @@ export class OsmPreferences {
|
|||
}
|
||||
}
|
||||
|
||||
public getPreference(key: string, defaultValue: string = undefined, prefix?: string, saveLocally = true) {
|
||||
return this.getPreferenceSeedFromlocal(key, defaultValue, { prefix, saveToLocalStorage: saveLocally })
|
||||
public getPreference(
|
||||
key: string,
|
||||
defaultValue: string = undefined,
|
||||
prefix?: string,
|
||||
saveLocally = true
|
||||
) {
|
||||
return this.getPreferenceSeedFromlocal(key, defaultValue, {
|
||||
prefix,
|
||||
saveToLocalStorage: saveLocally,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,7 +151,6 @@ export class OsmPreferences {
|
|||
* OsmPreferences.mergeDict({abc: "123", def: "123", "def:0": "456", "def:1":"789"}) // => {abc: "123", def: "123456789"}
|
||||
*/
|
||||
private static mergeDict(dict: Record<string, string>): Record<string, string> {
|
||||
|
||||
const keyParts: Record<string, Record<number, string>> = {}
|
||||
const endsWithNumber = /:[0-9]+$/
|
||||
for (const key of Object.keys(dict)) {
|
||||
|
@ -167,7 +174,6 @@ export class OsmPreferences {
|
|||
}
|
||||
subparts[""] = dict[key]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const newDict = {}
|
||||
|
@ -199,7 +205,7 @@ export class OsmPreferences {
|
|||
this.auth.xhr(
|
||||
{
|
||||
method: "GET",
|
||||
path: "/api/0.6/user/preferences"
|
||||
path: "/api/0.6/user/preferences",
|
||||
},
|
||||
(error, value: XMLDocument) => {
|
||||
if (error) {
|
||||
|
@ -220,7 +226,6 @@ export class OsmPreferences {
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
private static readonly endsWithNumber = /:[0-9]+$/
|
||||
|
||||
/**
|
||||
|
@ -234,7 +239,6 @@ export class OsmPreferences {
|
|||
*
|
||||
*/
|
||||
private static keysStartingWith(allKeys: string[], key: string): string[] {
|
||||
|
||||
const keys = allKeys.filter((k) => {
|
||||
if (k === key) {
|
||||
return true
|
||||
|
@ -300,7 +304,7 @@ export class OsmPreferences {
|
|||
{
|
||||
method: "DELETE",
|
||||
path: "/api/0.6/user/preferences/" + encodeURIComponent(k),
|
||||
headers: { "Content-Type": "text/plain" }
|
||||
headers: { "Content-Type": "text/plain" },
|
||||
},
|
||||
(error) => {
|
||||
if (error) {
|
||||
|
@ -342,9 +346,12 @@ export class OsmPreferences {
|
|||
}
|
||||
|
||||
try {
|
||||
|
||||
return this.osmConnection.interact("user/preferences/" + encodeURIComponent(k),
|
||||
"PUT", { "Content-Type": "text/plain" }, v)
|
||||
return this.osmConnection.interact(
|
||||
"user/preferences/" + encodeURIComponent(k),
|
||||
"PUT",
|
||||
{ "Content-Type": "text/plain" },
|
||||
v
|
||||
)
|
||||
} catch (e) {
|
||||
console.error("Could not upload preference due to", e)
|
||||
}
|
||||
|
@ -365,7 +372,13 @@ export class OsmPreferences {
|
|||
}
|
||||
|
||||
private async cleanup() {
|
||||
const prefixesToClean = ["mapcomplete-mapcomplete-", "mapcomplete-places-history", "unofficial-theme-", "mapcompleteplaces", "mapcompletethemes"] // TODO enable this one once the new system is in prod "mapcomplete-current-open-changeset-"]
|
||||
const prefixesToClean = [
|
||||
"mapcomplete-mapcomplete-",
|
||||
"mapcomplete-places-history",
|
||||
"unofficial-theme-",
|
||||
"mapcompleteplaces",
|
||||
"mapcompletethemes",
|
||||
] // TODO enable this one once the new system is in prod "mapcomplete-current-open-changeset-"]
|
||||
let somethingChanged = false
|
||||
for (const prefix of prefixesToClean) {
|
||||
const hasChange = await this.removeAllWithPrefix(prefix) // Don't inline - short-circuiting
|
||||
|
@ -381,10 +394,13 @@ export class OsmPreferences {
|
|||
for (const theme of themes.data) {
|
||||
const cs = this.osmConnection.getCurrentChangesetFor(theme)
|
||||
if (now.getTime() - cs.data.opened > 24 * 60 * 60 * 1000) {
|
||||
console.log("Clearing 'open changeset' for theme", theme, "; definitively expired by now")
|
||||
console.log(
|
||||
"Clearing 'open changeset' for theme",
|
||||
theme,
|
||||
"; definitively expired by now"
|
||||
)
|
||||
cs.set(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import osmtogeojson from "osmtogeojson"
|
|||
import { FeatureCollection, Geometry } from "geojson"
|
||||
import { OsmTags } from "../../Models/OsmFeature"
|
||||
|
||||
("use strict")
|
||||
;("use strict")
|
||||
/**
|
||||
* Interfaces overpass to get all the latest data
|
||||
*/
|
||||
|
|
|
@ -10,7 +10,7 @@ export type GeolocationPermissionState = "prompt" | "requested" | "granted" | "d
|
|||
export interface GeoLocationPointProperties extends GeolocationCoordinates {
|
||||
id: "gps" | string
|
||||
"user:location": "yes"
|
||||
date: string,
|
||||
date: string
|
||||
alpha?: string
|
||||
}
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ export default class SearchState {
|
|||
const results = themeSearch.data.search(query, 3)
|
||||
const deduped: MinimalThemeInformation[] = []
|
||||
for (const result of results) {
|
||||
if (deduped.some(th => th.id === result.id)) {
|
||||
if (deduped.some((th) => th.id === result.id)) {
|
||||
continue
|
||||
}
|
||||
deduped.push(result)
|
||||
|
|
|
@ -57,10 +57,7 @@ class RoundRobinStore<T> {
|
|||
this._index.set((i + 1) % this._maxCount)
|
||||
this._store.data[i] = t
|
||||
this._store.ping()
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export class OptionallySyncedHistory<T extends object | string> {
|
||||
|
@ -84,18 +81,21 @@ export class OptionallySyncedHistory<T extends object | string> {
|
|||
this._maxHistory = maxHistory
|
||||
this._isSame = isSame
|
||||
this.syncPreference = osmconnection.getPreference("preference-" + key + "-history", {
|
||||
defaultValue: "sync"
|
||||
defaultValue: "sync",
|
||||
})
|
||||
|
||||
this.syncedBackingStore = Stores.fromArray(
|
||||
Utils.TimesT(maxHistory, (i) => {
|
||||
const pref = osmconnection.getPreference(key + "-hist-" + i + "-")
|
||||
return UIEventSource.asObject<T>(pref, undefined)
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
const ringIndex = UIEventSource.asInt(osmconnection.getPreference(key + "-hist-round-robin", {
|
||||
defaultValue: "0"
|
||||
}))
|
||||
const ringIndex = UIEventSource.asInt(
|
||||
osmconnection.getPreference(key + "-hist-round-robin", {
|
||||
defaultValue: "0",
|
||||
})
|
||||
)
|
||||
this.syncedOrdered = new RoundRobinStore<T>(this.syncedBackingStore, ringIndex, 10)
|
||||
const local = (this.local = LocalStorageSource.getParsed<T[]>(key + "-history", []))
|
||||
const thisSession = (this.thisSession = new UIEventSource<T[]>(
|
||||
|
@ -104,7 +104,10 @@ export class OptionallySyncedHistory<T extends object | string> {
|
|||
))
|
||||
this.syncPreference.addCallback((syncmode) => {
|
||||
if (syncmode === "sync") {
|
||||
const list = [...thisSession.data, ...this.syncedOrdered.value.data].slice(0, maxHistory)
|
||||
const list = [...thisSession.data, ...this.syncedOrdered.value.data].slice(
|
||||
0,
|
||||
maxHistory
|
||||
)
|
||||
if (this._isSame) {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
for (let j = i + 1; j < list.length; j++) {
|
||||
|
@ -140,7 +143,9 @@ export class OptionallySyncedHistory<T extends object | string> {
|
|||
|
||||
public add(t: T) {
|
||||
if (this._isSame) {
|
||||
const alreadyNoted = this.getAppropriateStore().data.some(item => this._isSame(item, t))
|
||||
const alreadyNoted = this.getAppropriateStore().data.some((item) =>
|
||||
this._isSame(item, t)
|
||||
)
|
||||
if (alreadyNoted) {
|
||||
return
|
||||
}
|
||||
|
@ -154,7 +159,7 @@ export class OptionallySyncedHistory<T extends object | string> {
|
|||
}
|
||||
this.local.ping()
|
||||
} else if (this.syncPreference.data === "sync") {
|
||||
this.osmconnection.isLoggedIn.addCallbackAndRun(loggedIn => {
|
||||
this.osmconnection.isLoggedIn.addCallbackAndRun((loggedIn) => {
|
||||
// Wait until we are logged in and the settings are downloaded before adding the preference
|
||||
if (loggedIn) {
|
||||
this.syncedOrdered.add(t)
|
||||
|
@ -271,22 +276,29 @@ export default class UserRelatedState {
|
|||
this.a11y = this.osmConnection.getPreference("a11y")
|
||||
|
||||
this.mangroveIdentity = new MangroveIdentity(
|
||||
this.osmConnection.getPreference("identity", { defaultValue: undefined, prefix: "mangrove" }),
|
||||
this.osmConnection.getPreference("identity", {
|
||||
defaultValue: undefined,
|
||||
prefix: "mangrove",
|
||||
}),
|
||||
this.osmConnection.getPreference("identity-creation-date", {
|
||||
defaultValue: undefined,
|
||||
prefix: "mangrove"
|
||||
prefix: "mangrove",
|
||||
})
|
||||
)
|
||||
this.preferredBackgroundLayer = this.osmConnection.getPreference("preferred-background-layer")
|
||||
|
||||
this.addNewFeatureMode = this.osmConnection.getPreference("preferences-add-new-mode",
|
||||
{ defaultValue: "button_click_right" }
|
||||
this.preferredBackgroundLayer = this.osmConnection.getPreference(
|
||||
"preferred-background-layer"
|
||||
)
|
||||
|
||||
this.addNewFeatureMode = this.osmConnection.getPreference("preferences-add-new-mode", {
|
||||
defaultValue: "button_click_right",
|
||||
})
|
||||
this.showScale = UIEventSource.asBoolean(
|
||||
this.osmConnection.getPreference("preference-show-scale", { defaultValue: "false" })
|
||||
)
|
||||
|
||||
this.imageLicense = this.osmConnection.getPreference("pictures-license", { defaultValue: "CC0" })
|
||||
this.imageLicense = this.osmConnection.getPreference("pictures-license", {
|
||||
defaultValue: "CC0",
|
||||
})
|
||||
this.installedUserThemes = UserRelatedState.initInstalledUserThemes(osmConnection)
|
||||
this.translationMode = this.initTranslationMode()
|
||||
this.homeLocation = this.initHomeLocation()
|
||||
|
@ -370,8 +382,8 @@ export default class UserRelatedState {
|
|||
} catch (e) {
|
||||
console.warn(
|
||||
"Removing theme " +
|
||||
id +
|
||||
" as it could not be parsed from the preferences; the content is:",
|
||||
id +
|
||||
" as it could not be parsed from the preferences; the content is:",
|
||||
str
|
||||
)
|
||||
pref.setData(null)
|
||||
|
@ -401,7 +413,7 @@ export default class UserRelatedState {
|
|||
icon: layout.icon,
|
||||
title: layout.title.translations,
|
||||
shortDescription: layout.shortDescription.translations,
|
||||
definition: layout["definition"]
|
||||
definition: layout["definition"],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -456,13 +468,13 @@ export default class UserRelatedState {
|
|||
id: "home",
|
||||
"user:home": "yes",
|
||||
_lon: homeLonLat[0],
|
||||
_lat: homeLonLat[1]
|
||||
_lat: homeLonLat[1],
|
||||
},
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: homeLonLat
|
||||
}
|
||||
}
|
||||
coordinates: homeLonLat,
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
return new StaticFeatureSource(feature)
|
||||
|
@ -484,7 +496,7 @@ export default class UserRelatedState {
|
|||
_applicationOpened: new Date().toISOString(),
|
||||
_supports_sharing:
|
||||
typeof window === "undefined" ? "no" : window.navigator.share ? "yes" : "no",
|
||||
_iframe: Utils.isIframe ? "yes" : "no"
|
||||
_iframe: Utils.isIframe ? "yes" : "no",
|
||||
})
|
||||
if (!Utils.runningFromConsole) {
|
||||
amendedPrefs.data["_host"] = window.location.host
|
||||
|
@ -532,18 +544,18 @@ export default class UserRelatedState {
|
|||
const zenLinks: { link: string; id: string }[] = Utils.NoNull([
|
||||
hasMissingTheme
|
||||
? {
|
||||
id: "theme:" + layout.id,
|
||||
link: LinkToWeblate.hrefToWeblateZen(
|
||||
language,
|
||||
"themes",
|
||||
layout.id
|
||||
)
|
||||
}
|
||||
id: "theme:" + layout.id,
|
||||
link: LinkToWeblate.hrefToWeblateZen(
|
||||
language,
|
||||
"themes",
|
||||
layout.id
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
...missingLayers.map((id) => ({
|
||||
id: "layer:" + id,
|
||||
link: LinkToWeblate.hrefToWeblateZen(language, "layers", id)
|
||||
}))
|
||||
link: LinkToWeblate.hrefToWeblateZen(language, "layers", id),
|
||||
})),
|
||||
])
|
||||
const untranslated_count = untranslated.length
|
||||
amendedPrefs.data["_translation_total"] = "" + total
|
||||
|
|
|
@ -1,14 +1,42 @@
|
|||
import { Utils } from "../../Utils"
|
||||
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
|
||||
export class ThemeMetaTagging {
|
||||
public static readonly themeName = "usersettings"
|
||||
public static readonly themeName = "usersettings"
|
||||
|
||||
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
|
||||
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? '' )
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) )
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) )
|
||||
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
|
||||
feat.properties['__current_backgroun'] = 'initial_value'
|
||||
}
|
||||
}
|
||||
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
|
||||
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
|
||||
feat.properties._description
|
||||
.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
|
||||
?.at(1)
|
||||
)
|
||||
Utils.AddLazyProperty(
|
||||
feat.properties,
|
||||
"_d",
|
||||
() => feat.properties._description?.replace(/</g, "<")?.replace(/>/g, ">") ?? ""
|
||||
)
|
||||
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () =>
|
||||
((feat) => {
|
||||
const e = document.createElement("div")
|
||||
e.innerHTML = feat.properties._d
|
||||
return Array.from(e.getElementsByTagName("a")).filter(
|
||||
(a) => a.href.match(/mastodon|en.osm.town/) !== null
|
||||
)[0]?.href
|
||||
})(feat)
|
||||
)
|
||||
Utils.AddLazyProperty(feat.properties, "_mastodon_link", () =>
|
||||
((feat) => {
|
||||
const e = document.createElement("div")
|
||||
e.innerHTML = feat.properties._d
|
||||
return Array.from(e.getElementsByTagName("a")).filter(
|
||||
(a) => a.getAttribute("rel")?.indexOf("me") >= 0
|
||||
)[0]?.href
|
||||
})(feat)
|
||||
)
|
||||
Utils.AddLazyProperty(
|
||||
feat.properties,
|
||||
"_mastodon_candidate",
|
||||
() => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a
|
||||
)
|
||||
feat.properties["__current_backgroun"] = "initial_value"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,12 +92,12 @@ export class TagUtils {
|
|||
"!~i~": {
|
||||
name: "Value does *not* match case-invariant regex",
|
||||
overpassSupport: true,
|
||||
docs: "A tag can also be tested against a regex with `key~i~regex`, where the case of the value will be ignored. The regex is still matched against the _entire_ value (thus: a `^` and `$` are automatically added to start and end). This filter returns true if the value does *not* match"
|
||||
docs: "A tag can also be tested against a regex with `key~i~regex`, where the case of the value will be ignored. The regex is still matched against the _entire_ value (thus: a `^` and `$` are automatically added to start and end). This filter returns true if the value does *not* match",
|
||||
},
|
||||
"~~": {
|
||||
name: "Key and value should match given regex",
|
||||
overpassSupport: true,
|
||||
docs: "Both the `key` and `value` part of this specification are interpreted as regexes, both the key and value must completely match their respective regexes (thus: a `^` and `$` are automatically added to start and end)"
|
||||
docs: "Both the `key` and `value` part of this specification are interpreted as regexes, both the key and value must completely match their respective regexes (thus: a `^` and `$` are automatically added to start and end)",
|
||||
},
|
||||
"~i~~": {
|
||||
name: "Key and value should match a given regex; value is case-invariant",
|
||||
|
|
|
@ -37,10 +37,12 @@ export class Stores {
|
|||
*/
|
||||
public static FromPromise<T>(promise: Promise<T>): Store<T | undefined> {
|
||||
const src = new UIEventSource<T>(undefined)
|
||||
promise?.catch((err): undefined => {
|
||||
console.warn("Promise failed:", err)
|
||||
return undefined
|
||||
})?.then((d) => src.setData(d))
|
||||
promise
|
||||
?.catch((err): undefined => {
|
||||
console.warn("Promise failed:", err)
|
||||
return undefined
|
||||
})
|
||||
?.then((d) => src.setData(d))
|
||||
return src
|
||||
}
|
||||
|
||||
|
@ -109,14 +111,14 @@ export class Stores {
|
|||
}
|
||||
|
||||
public static fromArray<T>(sources: ReadonlyArray<UIEventSource<T>>): UIEventSource<T[]> {
|
||||
const src = new UIEventSource<T[]>(sources.map(s => s.data))
|
||||
const src = new UIEventSource<T[]>(sources.map((s) => s.data))
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
sources[i].addCallback(content => {
|
||||
sources[i].addCallback((content) => {
|
||||
src.data[i] = content
|
||||
src.ping()
|
||||
})
|
||||
}
|
||||
src.addCallbackD(contents => {
|
||||
src.addCallbackD((contents) => {
|
||||
for (let i = 0; i < contents.length; i++) {
|
||||
sources[i].setData(contents[i])
|
||||
}
|
||||
|
@ -125,9 +127,9 @@ export class Stores {
|
|||
}
|
||||
|
||||
public static fromStoresArray<T>(sources: ReadonlyArray<Store<T>>): Store<T[]> {
|
||||
const src = new UIEventSource<T[]>(sources.map(s => s.data))
|
||||
const src = new UIEventSource<T[]>(sources.map((s) => s.data))
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
sources[i].addCallback(content => {
|
||||
sources[i].addCallback((content) => {
|
||||
src.data[i] = content
|
||||
src.ping()
|
||||
})
|
||||
|
@ -399,8 +401,7 @@ export class ImmutableStore<T> extends Store<T> {
|
|||
this.data = data
|
||||
}
|
||||
|
||||
private static readonly pass: () => void = () => {
|
||||
}
|
||||
private static readonly pass: () => void = () => {}
|
||||
|
||||
addCallback(_: (data: T) => void): () => void {
|
||||
// pass: data will never change
|
||||
|
@ -678,8 +679,7 @@ class MappedStore<TIn, T> extends Store<T> {
|
|||
}
|
||||
|
||||
export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||
private static readonly pass: () => void = () => {
|
||||
}
|
||||
private static readonly pass: () => void = () => {}
|
||||
public data: T
|
||||
_callbacks: ListenerTracker<T> = new ListenerTracker<T>()
|
||||
|
||||
|
@ -832,7 +832,14 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
|||
try {
|
||||
return <T>JSON.parse(str)
|
||||
} catch (e) {
|
||||
console.error("Could not parse value", str, "due to", e, "; the underlying data store has tag", stringUIEventSource.tag)
|
||||
console.error(
|
||||
"Could not parse value",
|
||||
str,
|
||||
"due to",
|
||||
e,
|
||||
"; the underlying data store has tag",
|
||||
stringUIEventSource.tag
|
||||
)
|
||||
return defaultV
|
||||
}
|
||||
},
|
||||
|
|
|
@ -166,7 +166,11 @@ class ImagesFromPanoramaxFetcher implements ImageFetcher {
|
|||
private readonly _radius: number
|
||||
private readonly _panoramax: Panoramax
|
||||
name: string = "panoramax"
|
||||
public static readonly apiUrls: ReadonlyArray<string> = ["https://panoramax.openstreetmap.fr", "https://api.panoramax.xyz", "https://panoramax.mapcomplete.org"]
|
||||
public static readonly apiUrls: ReadonlyArray<string> = [
|
||||
"https://panoramax.openstreetmap.fr",
|
||||
"https://api.panoramax.xyz",
|
||||
"https://panoramax.mapcomplete.org",
|
||||
]
|
||||
|
||||
constructor(url?: string, radius: number = 100) {
|
||||
this._radius = radius
|
||||
|
@ -286,7 +290,7 @@ class MapillaryFetcher implements ImageFetcher {
|
|||
mapillary: img.id,
|
||||
},
|
||||
details: {
|
||||
isSpherical: this._panoramas === "only"
|
||||
isSpherical: this._panoramas === "only",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -298,10 +302,12 @@ type P4CService = (typeof P4CImageFetcher.services)[number]
|
|||
|
||||
export class CombinedFetcher {
|
||||
private readonly sources: ReadonlyArray<CachedFetcher>
|
||||
public static apiUrls = [...P4CImageFetcher.apiUrls,
|
||||
Imgur.apiUrl, ...Imgur.supportingUrls,
|
||||
public static apiUrls = [
|
||||
...P4CImageFetcher.apiUrls,
|
||||
Imgur.apiUrl,
|
||||
...Imgur.supportingUrls,
|
||||
...MapillaryFetcher.apiUrls,
|
||||
...ImagesFromPanoramaxFetcher.apiUrls
|
||||
...ImagesFromPanoramaxFetcher.apiUrls,
|
||||
]
|
||||
|
||||
constructor(radius: number, maxage: Date, indexedFeatures: IndexedFeatureSource) {
|
||||
|
@ -313,14 +319,15 @@ export class CombinedFetcher {
|
|||
new MapillaryFetcher({
|
||||
max_images: 25,
|
||||
start_captured_at: maxage,
|
||||
panoramas: "only"
|
||||
panoramas: "only",
|
||||
}),
|
||||
new MapillaryFetcher({
|
||||
max_images: 25,
|
||||
start_captured_at: maxage,
|
||||
panoramas: "no"
|
||||
}), new P4CImageFetcher("mapillary"),
|
||||
new P4CImageFetcher("wikicommons")
|
||||
panoramas: "no",
|
||||
}),
|
||||
new P4CImageFetcher("mapillary"),
|
||||
new P4CImageFetcher("wikicommons"),
|
||||
].map((f) => new CachedFetcher(f))
|
||||
}
|
||||
|
||||
|
|
|
@ -39,20 +39,24 @@ export class AvailableRasterLayers {
|
|||
url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
attribution: {
|
||||
text: "OpenStreetMap",
|
||||
url: "https://openStreetMap.org/copyright"
|
||||
url: "https://openStreetMap.org/copyright",
|
||||
},
|
||||
best: true,
|
||||
max_zoom: 19,
|
||||
min_zoom: 0,
|
||||
category: "osmbasedmap"
|
||||
category: "osmbasedmap",
|
||||
}
|
||||
public static readonly osmCarto: RasterLayerPolygon = {
|
||||
type: "Feature",
|
||||
properties: AvailableRasterLayers.osmCartoProperties,
|
||||
geometry: BBox.global.asGeometry()
|
||||
geometry: BBox.global.asGeometry(),
|
||||
}
|
||||
|
||||
public static allAvailableGlobalLayers = new Set([...AvailableRasterLayers.globalLayers, AvailableRasterLayers.osmCarto, AvailableRasterLayers.bing])
|
||||
public static allAvailableGlobalLayers = new Set([
|
||||
...AvailableRasterLayers.globalLayers,
|
||||
AvailableRasterLayers.osmCarto,
|
||||
AvailableRasterLayers.bing,
|
||||
])
|
||||
|
||||
private static initGlobalLayers(): RasterLayerPolygon[] {
|
||||
const gl: RasterLayerProperties[] = (globallayers["default"] ?? globallayers).layers.filter(
|
||||
|
@ -75,8 +79,6 @@ export class AvailableRasterLayers {
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The default background layer that any theme uses which does not explicitly define a background
|
||||
*/
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { DesugaringStep } from "./Conversion"
|
||||
import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
|
||||
import { LayerConfigJson } from "../Json/LayerConfigJson"
|
||||
import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
|
||||
import {
|
||||
MappingConfigJson,
|
||||
QuestionableTagRenderingConfigJson,
|
||||
} from "../Json/QuestionableTagRenderingConfigJson"
|
||||
import { ConversionContext } from "./ConversionContext"
|
||||
import { Translation } from "../../../UI/i18n/Translation"
|
||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
import { Concat, DesugaringContext, DesugaringStep, Each, FirstOf, Fuse, On, SetDefault } from "./Conversion"
|
||||
import {
|
||||
Concat,
|
||||
DesugaringContext,
|
||||
DesugaringStep,
|
||||
Each,
|
||||
FirstOf,
|
||||
Fuse,
|
||||
On,
|
||||
SetDefault,
|
||||
} from "./Conversion"
|
||||
import { LayerConfigJson } from "../Json/LayerConfigJson"
|
||||
import { MinimalTagRenderingConfigJson, TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
|
||||
import {
|
||||
MinimalTagRenderingConfigJson,
|
||||
TagRenderingConfigJson,
|
||||
} from "../Json/TagRenderingConfigJson"
|
||||
import { Utils } from "../../../Utils"
|
||||
import RewritableConfigJson from "../Json/RewritableConfigJson"
|
||||
import SpecialVisualizations from "../../../UI/SpecialVisualizations"
|
||||
|
|
|
@ -271,7 +271,6 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
|
|||
*/
|
||||
default?: string
|
||||
|
||||
|
||||
/**
|
||||
* question: If this key shared and distinguished by a postfix, what is the postfix?
|
||||
* This option is used specifically for `charge`, where the cost is indicated with `/item`.
|
||||
|
|
|
@ -381,14 +381,11 @@ export default class LayerConfig extends WithContextLoader {
|
|||
* @private
|
||||
*/
|
||||
private generateDocumentationQuickTable(): string {
|
||||
|
||||
|
||||
return MarkdownUtils.table(
|
||||
["id", "question", "labels", "freeform key"],
|
||||
this.tagRenderings
|
||||
.filter(tr => tr.labels.indexOf("ignore_docs") < 0)
|
||||
.map(tr => {
|
||||
|
||||
.filter((tr) => tr.labels.indexOf("ignore_docs") < 0)
|
||||
.map((tr) => {
|
||||
let key = "_Multiple choice only_"
|
||||
if (tr.freeform) {
|
||||
const type = `[${tr.freeform.type}](../SpecialInputElements.md#${tr.freeform.type})`
|
||||
|
@ -419,14 +416,10 @@ export default class LayerConfig extends WithContextLoader {
|
|||
`[${tr.id}](#${tr.id}) ${origDef}`,
|
||||
Utils.NoNull([q, r, options]).join("<br/>"),
|
||||
tr.labels.join(", "),
|
||||
key
|
||||
|
||||
key,
|
||||
]
|
||||
|
||||
|
||||
})
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
public generateDocumentation(
|
||||
|
|
|
@ -5,7 +5,10 @@ import { TagUtils } from "../../Logic/Tags/TagUtils"
|
|||
import { And } from "../../Logic/Tags/And"
|
||||
import { Utils } from "../../Utils"
|
||||
import { Tag } from "../../Logic/Tags/Tag"
|
||||
import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson"
|
||||
import {
|
||||
MappingConfigJson,
|
||||
QuestionableTagRenderingConfigJson,
|
||||
} from "./Json/QuestionableTagRenderingConfigJson"
|
||||
import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
|
||||
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
|
||||
import { RegexTag } from "../../Logic/Tags/RegexTag"
|
||||
|
@ -697,7 +700,10 @@ export default class TagRenderingConfig {
|
|||
* TagRenderingConfig.splitPostfixDistinguished(" €10 / day ") // => {value: "€10", denomination: "day"}
|
||||
*
|
||||
*/
|
||||
private static splitPostfixDistinguished(part: string): { value: string, denomination: string } {
|
||||
private static splitPostfixDistinguished(part: string): {
|
||||
value: string
|
||||
denomination: string
|
||||
} {
|
||||
const i = part.indexOf("/")
|
||||
if (i < 0) {
|
||||
return { value: part, denomination: "" }
|
||||
|
|
|
@ -43,7 +43,9 @@ export class UserMapFeatureswitchState extends WithUserRelatedState {
|
|||
readonly geolocationState: GeoLocationState
|
||||
readonly geolocation: GeoLocationHandler
|
||||
readonly geolocationControl: GeolocationControlState
|
||||
readonly historicalUserLocations: WritableFeatureSource<Feature<Point, GeoLocationPointProperties>>
|
||||
readonly historicalUserLocations: WritableFeatureSource<
|
||||
Feature<Point, GeoLocationPointProperties>
|
||||
>
|
||||
|
||||
readonly availableLayers: { store: Store<RasterLayerPolygon[]> }
|
||||
readonly currentView: FeatureSource<Feature<Polygon>>
|
||||
|
|
|
@ -55,7 +55,7 @@ export class WithImageState extends WithGuiState implements SpecialVisualization
|
|||
featureSwitches: this.featureSwitches,
|
||||
selectedElement: this.selectedElement,
|
||||
indexedFeatures: this.indexedFeatures,
|
||||
guistate: this.guistate
|
||||
guistate: this.guistate,
|
||||
})
|
||||
new PendingChangesUploader(this.changes, this.selectedElement, this.imageUploadManager)
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ export class Tiles {
|
|||
static asGeojson(zIndex: number, x?: number, y?: number): Feature<Polygon> {
|
||||
let z = zIndex
|
||||
if (x === undefined) {
|
||||
[z, x, y] = Tiles.tile_from_index(zIndex)
|
||||
;[z, x, y] = Tiles.tile_from_index(zIndex)
|
||||
}
|
||||
const bounds = Tiles.tile_bounds_lon_lat(z, x, y)
|
||||
return new BBox(bounds).asGeoJson()
|
||||
|
@ -161,10 +161,10 @@ export class Tiles {
|
|||
private static lat2tile(lat: number, zoom: number): number {
|
||||
return Math.floor(
|
||||
((1 -
|
||||
Math.log(Math.tan((lat * Math.PI) / 180) + 1 / Math.cos((lat * Math.PI) / 180)) /
|
||||
Math.log(Math.tan((lat * Math.PI) / 180) + 1 / Math.cos((lat * Math.PI) / 180)) /
|
||||
Math.PI) /
|
||||
2) *
|
||||
Math.pow(2, zoom)
|
||||
Math.pow(2, zoom)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,7 +165,7 @@
|
|||
<Page {onlyLink} shown={pg.failedImages} bodyPadding="p-0 pb-4">
|
||||
<svelte:fragment slot="header">
|
||||
<PhotoIcon />
|
||||
<Tr t={Translations.t.imageQueue.menu.Subs({count: $nrOfFailedImages.length})} />
|
||||
<Tr t={Translations.t.imageQueue.menu.Subs({ count: $nrOfFailedImages.length })} />
|
||||
</svelte:fragment>
|
||||
<QueuedImagesView {state} />
|
||||
</Page>
|
||||
|
|
|
@ -35,7 +35,9 @@
|
|||
export let attributionFormat: "minimal" | "medium" | "large" = "medium"
|
||||
let previewedImage: UIEventSource<Partial<ProvidedImage>> = MenuState.previewedImage
|
||||
export let canZoom = previewedImage !== undefined
|
||||
export let nearbyFeatures: Feature<Geometry, HotspotProperties>[] | Store<Feature<Geometry, HotspotProperties>[]> = []
|
||||
export let nearbyFeatures:
|
||||
| Feature<Geometry, HotspotProperties>[]
|
||||
| Store<Feature<Geometry, HotspotProperties>[]> = []
|
||||
|
||||
let loaded = false
|
||||
let showBigPreview = new UIEventSource(false)
|
||||
|
@ -49,7 +51,7 @@
|
|||
previewedImage.addCallbackAndRun((previewedImage) => {
|
||||
showBigPreview.set(
|
||||
previewedImage !== undefined &&
|
||||
(previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url)
|
||||
(previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url)
|
||||
)
|
||||
})
|
||||
)
|
||||
|
@ -67,16 +69,15 @@
|
|||
type: "Feature",
|
||||
properties: {
|
||||
id: image.id,
|
||||
rotation: image.rotation
|
||||
rotation: image.rotation,
|
||||
},
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [image.lon, image.lat]
|
||||
}
|
||||
coordinates: [image.lon, image.lat],
|
||||
},
|
||||
}
|
||||
state?.geocodedImages.set([f])
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<Popup shown={showBigPreview} bodyPadding="p-0" dismissable={true}>
|
||||
|
@ -135,10 +136,12 @@
|
|||
/>
|
||||
|
||||
{#if image.isSpherical}
|
||||
<div class="absolute top-0 left-0 w-full h-full flex justify-center items-center pointer-events-none">
|
||||
<div class="bg-black opacity-50 rounded-full p-[3.25rem]">
|
||||
<div class="w-0 h-0 relative flex items-center justify-center">
|
||||
<Panorama360 class="absolute w-16 h-16" color="#ffffff" />
|
||||
<div
|
||||
class="pointer-events-none absolute left-0 top-0 flex h-full w-full items-center justify-center"
|
||||
>
|
||||
<div class="rounded-full bg-black p-[3.25rem] opacity-50">
|
||||
<div class="relative flex h-0 w-0 items-center justify-center">
|
||||
<Panorama360 class="absolute h-16 w-16" color="#ffffff" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -157,7 +160,6 @@
|
|||
<ImageAttribution {image} {attributionFormat} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{:else if image.status === "hidden"}
|
||||
<div class="subtle">This image has been reported</div>
|
||||
{/if}
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
type: "Feature",
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: GeoOperations.centerpointCoordinates(feature)
|
||||
coordinates: GeoOperations.centerpointCoordinates(feature),
|
||||
},
|
||||
properties: {
|
||||
name: layer?.title?.GetRenderValue(feature.properties)?.Subs(feature.properties)?.txt ?? feature?.properties?.name,
|
||||
focus: true
|
||||
}
|
||||
name:
|
||||
layer?.title?.GetRenderValue(feature.properties)?.Subs(feature.properties)?.txt ??
|
||||
feature?.properties?.name,
|
||||
focus: true,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
|
||||
export let image: Partial<ProvidedImage> & { id: string; url: string }
|
||||
export let clss: string = undefined
|
||||
export let nearbyFeatures: Feature<Geometry, HotspotProperties>[] | Store<Feature<Geometry, HotspotProperties>[]> = []
|
||||
export let nearbyFeatures:
|
||||
| Feature<Geometry, HotspotProperties>[]
|
||||
| Store<Feature<Geometry, HotspotProperties>[]> = []
|
||||
|
||||
let isLoaded = new UIEventSource(false)
|
||||
console.log(">>> slots are", $$slots)
|
||||
|
@ -37,17 +39,17 @@
|
|||
</div>
|
||||
|
||||
{#if $$slots["dot-menu-actions"]}
|
||||
<DotMenu dotsPosition="top-0 left-0" dotsSize="w-8 h-8" hideBackground>
|
||||
<slot name="dot-menu-actions">
|
||||
<button
|
||||
class="no-image-background pointer-events-auto flex items-center"
|
||||
on:click={() => ImageProvider.offerImageAsDownload(image)}
|
||||
>
|
||||
<DownloadIcon class="h-6 w-6 px-2 opacity-100" />
|
||||
<Tr t={Translations.t.general.download.downloadImage} />
|
||||
</button>
|
||||
</slot>
|
||||
</DotMenu>
|
||||
<DotMenu dotsPosition="top-0 left-0" dotsSize="w-8 h-8" hideBackground>
|
||||
<slot name="dot-menu-actions">
|
||||
<button
|
||||
class="no-image-background pointer-events-auto flex items-center"
|
||||
on:click={() => ImageProvider.offerImageAsDownload(image)}
|
||||
>
|
||||
<DownloadIcon class="h-6 w-6 px-2 opacity-100" />
|
||||
<Tr t={Translations.t.general.download.downloadImage} />
|
||||
</button>
|
||||
</slot>
|
||||
</DotMenu>
|
||||
{/if}
|
||||
<div
|
||||
class="pointer-events-none absolute bottom-0 left-0 flex w-full flex-wrap items-end justify-between"
|
||||
|
|
|
@ -13,8 +13,9 @@
|
|||
import type { Feature, Geometry, Point } from "geojson"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
|
||||
|
||||
export let nearbyFeatures: Feature<Geometry, HotspotProperties>[] | Store<Feature<Geometry, HotspotProperties>[]> = []
|
||||
export let nearbyFeatures:
|
||||
| Feature<Geometry, HotspotProperties>[]
|
||||
| Store<Feature<Geometry, HotspotProperties>[]> = []
|
||||
export let image: Partial<ProvidedImage>
|
||||
let panzoomInstance = undefined
|
||||
let panzoomEl: HTMLElement
|
||||
|
@ -34,17 +35,15 @@
|
|||
if (Array.isArray(nearbyFeatures)) {
|
||||
viewer.setNearbyFeatures(nearbyFeatures)
|
||||
} else {
|
||||
nearbyFeatures.addCallbackAndRunD(feats => {
|
||||
nearbyFeatures.addCallbackAndRunD((feats) => {
|
||||
viewer.setNearbyFeatures(feats)
|
||||
})
|
||||
}
|
||||
isLoaded.set(true)
|
||||
|
||||
}
|
||||
|
||||
$: {
|
||||
if (image.isSpherical) {
|
||||
|
||||
initPhotosphere()
|
||||
} else if (panzoomEl) {
|
||||
panzoomInstance = panzoom(panzoomEl, {
|
||||
|
@ -52,7 +51,7 @@
|
|||
boundsPadding: 0.49,
|
||||
minZoom: 0.1,
|
||||
maxZoom: 25,
|
||||
initialZoom: 1.0
|
||||
initialZoom: 1.0,
|
||||
})
|
||||
} else {
|
||||
panzoomInstance?.dispose()
|
||||
|
@ -61,17 +60,17 @@
|
|||
</script>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="./css/pannellum.css">
|
||||
<link rel="stylesheet" href="./css/pannellum.css" />
|
||||
</head>
|
||||
{#if image.isSpherical}
|
||||
<div bind:this={viewerEl} class="w-full h-full" />
|
||||
<div bind:this={viewerEl} class="h-full w-full" />
|
||||
{:else}
|
||||
<img
|
||||
bind:this={panzoomEl}
|
||||
class="panzoom-image h-fit max-w-fit"
|
||||
on:load={() => {
|
||||
isLoaded?.setData(true)
|
||||
}}
|
||||
isLoaded?.setData(true)
|
||||
}}
|
||||
src={image.url_hd ?? image.url}
|
||||
/>
|
||||
{/if}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
export let linkable = true
|
||||
let targetValue = Object.values(image.osmTags)[0]
|
||||
let isLinked = new UIEventSource(Object.values(tags.data).some((v) => targetValue === v))
|
||||
isLinked.addCallbackAndRun(linked => {
|
||||
isLinked.addCallbackAndRun((linked) => {
|
||||
if (linked) {
|
||||
MenuState.previewedImage.set(undefined)
|
||||
}
|
||||
|
@ -43,7 +43,7 @@
|
|||
provider: AllImageProviders.byName(image.provider),
|
||||
date: new Date(image.date),
|
||||
id: Object.values(image.osmTags)[0],
|
||||
isSpherical: image.details.isSpherical
|
||||
isSpherical: image.details.isSpherical,
|
||||
}
|
||||
|
||||
async function applyLink(isLinked: boolean) {
|
||||
|
@ -54,7 +54,7 @@
|
|||
if (isLinked) {
|
||||
const action = new LinkImageAction(currentTags.id, key, url, tags, {
|
||||
theme: tags.data._orig_theme ?? state.theme.id,
|
||||
changeType: "link-image"
|
||||
changeType: "link-image",
|
||||
})
|
||||
await state.changes.applyAction(action)
|
||||
} else {
|
||||
|
@ -63,7 +63,7 @@
|
|||
if (v === url) {
|
||||
const action = new ChangeTagAction(currentTags.id, new Tag(k, ""), currentTags, {
|
||||
theme: tags.data._orig_theme ?? state.theme.id,
|
||||
changeType: "remove-image"
|
||||
changeType: "remove-image",
|
||||
})
|
||||
state.changes.applyAction(action)
|
||||
}
|
||||
|
@ -99,9 +99,7 @@
|
|||
attributionFormat="minimal"
|
||||
>
|
||||
<svelte:fragment slot="dot-menu-actions">
|
||||
|
||||
<LoginToggle {state} silentFail={true} hiddenFail={true}>
|
||||
|
||||
{#if linkable}
|
||||
<label>
|
||||
<input bind:checked={$isLinked} type="checkbox" />
|
||||
|
@ -110,7 +108,6 @@
|
|||
{/if}
|
||||
</LoginToggle>
|
||||
</svelte:fragment>
|
||||
|
||||
</AttributedImage>
|
||||
<LoginToggle {state} silentFail={true}>
|
||||
{#if linkable}
|
||||
|
|
|
@ -46,8 +46,7 @@
|
|||
(pics: P4CPicture[]) =>
|
||||
pics
|
||||
.filter(
|
||||
(p: P4CPicture) =>
|
||||
!loadedImages.data.has(p.pictureUrl) // We don't show any image which is already linked
|
||||
(p: P4CPicture) => !loadedImages.data.has(p.pictureUrl) // We don't show any image which is already linked
|
||||
)
|
||||
.slice(0, 25),
|
||||
[loadedImages]
|
||||
|
@ -60,15 +59,15 @@
|
|||
type: "Feature",
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [p4c.coordinates.lng, p4c.coordinates.lat]
|
||||
coordinates: [p4c.coordinates.lng, p4c.coordinates.lat],
|
||||
},
|
||||
properties: <PanoramaView>{
|
||||
id: p4c.pictureUrl,
|
||||
url: p4c.pictureUrl,
|
||||
northOffset: p4c.direction,
|
||||
rotation: p4c.direction,
|
||||
spherical: p4c.details.isSpherical ? "yes" : "no"
|
||||
}
|
||||
spherical: p4c.details.isSpherical ? "yes" : "no",
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -80,14 +79,14 @@
|
|||
type: "Feature",
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [s.coordinates.lng, s.coordinates.lat]
|
||||
coordinates: [s.coordinates.lng, s.coordinates.lat],
|
||||
},
|
||||
properties: {
|
||||
id: s.pictureUrl,
|
||||
selected: "yes",
|
||||
rotation: s.direction
|
||||
}
|
||||
}
|
||||
rotation: s.direction,
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
|
@ -112,7 +111,7 @@
|
|||
rotation: state.mapProperties.rotation,
|
||||
pitch: state.mapProperties.pitch,
|
||||
zoom: new UIEventSource<number>(16),
|
||||
location: new UIEventSource({ lon, lat })
|
||||
location: new UIEventSource({ lon, lat }),
|
||||
})
|
||||
|
||||
const geocodedImageLayer = new LayerConfig(<LayerConfigJson>geocoded_image)
|
||||
|
@ -123,7 +122,7 @@
|
|||
onClick: (feature) => {
|
||||
console.log("CLicked:", feature.properties)
|
||||
highlighted.set(feature.properties.id)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
ShowDataLayer.showMultipleLayers(map, new StaticFeatureSource([feature]), state.theme.layers)
|
||||
|
@ -146,24 +145,29 @@
|
|||
layer: geocodedImageLayer,
|
||||
onClick: (feature) => {
|
||||
highlighted.set(feature.properties.id)
|
||||
}
|
||||
},
|
||||
})
|
||||
let nearbyFeatures: Store<Feature[]> = asFeatures.map(nearbyPoints => {
|
||||
return [{
|
||||
type: "Feature",
|
||||
geometry: { type: "Point", coordinates: GeoOperations.centerpointCoordinates(feature) },
|
||||
properties: {
|
||||
name: layer.title?.GetRenderValue(feature.properties).Subs(feature.properties).txt,
|
||||
focus: true
|
||||
}
|
||||
}, ...nearbyPoints.filter(p => p.properties.spherical === "yes").map(f => ({
|
||||
...f, properties: {
|
||||
name: "Nearby panorama",
|
||||
pitch: "auto",
|
||||
type: "scene",
|
||||
gotoPanorama: f
|
||||
}
|
||||
}))
|
||||
let nearbyFeatures: Store<Feature[]> = asFeatures.map((nearbyPoints) => {
|
||||
return [
|
||||
{
|
||||
type: "Feature",
|
||||
geometry: { type: "Point", coordinates: GeoOperations.centerpointCoordinates(feature) },
|
||||
properties: {
|
||||
name: layer.title?.GetRenderValue(feature.properties).Subs(feature.properties).txt,
|
||||
focus: true,
|
||||
},
|
||||
},
|
||||
...nearbyPoints
|
||||
.filter((p) => p.properties.spherical === "yes")
|
||||
.map((f) => ({
|
||||
...f,
|
||||
properties: {
|
||||
name: "Nearby panorama",
|
||||
pitch: "auto",
|
||||
type: "scene",
|
||||
gotoPanorama: f,
|
||||
},
|
||||
})),
|
||||
]
|
||||
})
|
||||
|
||||
|
@ -204,7 +208,16 @@
|
|||
selected.set(undefined)
|
||||
}}
|
||||
>
|
||||
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} {highlighted} {nearbyFeatures} />
|
||||
<LinkableImage
|
||||
{tags}
|
||||
{image}
|
||||
{state}
|
||||
{feature}
|
||||
{layer}
|
||||
{linkable}
|
||||
{highlighted}
|
||||
{nearbyFeatures}
|
||||
/>
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -13,40 +13,45 @@
|
|||
export let imageArguments: ImageUploadArguments
|
||||
let confirmDelete = new UIEventSource(false)
|
||||
|
||||
|
||||
function del() {
|
||||
queue.delete(imageArguments)
|
||||
}
|
||||
|
||||
const t = Translations.t
|
||||
let src = undefined
|
||||
try{
|
||||
|
||||
try {
|
||||
src = URL.createObjectURL(imageArguments.blob)
|
||||
}catch (e) {
|
||||
} catch (e) {
|
||||
console.error("Could not create an ObjectURL for blob", imageArguments.blob)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="low-interaction rounded border-interactive w-fit p-2 m-1 flex flex-col">
|
||||
|
||||
<img class="max-w-64 w-auto max-h-64 w-auto" {src} />
|
||||
{imageArguments.featureId} {imageArguments.layoutId}
|
||||
<button class="as-link self-end" on:click={() => {confirmDelete.set(true)}}>
|
||||
<div class="low-interaction border-interactive m-1 flex w-fit flex-col rounded p-2">
|
||||
<img class="max-h-64 w-auto w-auto max-w-64" {src} />
|
||||
{imageArguments.featureId}
|
||||
{imageArguments.layoutId}
|
||||
<button
|
||||
class="as-link self-end"
|
||||
on:click={() => {
|
||||
confirmDelete.set(true)
|
||||
}}
|
||||
>
|
||||
<TrashIcon class="w-4" />
|
||||
<Tr t={t.imageQueue.delete} />
|
||||
</button>
|
||||
<Popup shown={confirmDelete} dismissable={true}>
|
||||
<Page shown={confirmDelete}>
|
||||
<svelte:fragment slot="header">
|
||||
<TrashIcon class="w-8 m-1" />
|
||||
<TrashIcon class="m-1 w-8" />
|
||||
<Tr t={t.imageQueue.confirmDeleteTitle} />
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="flex flex-col ">
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="flex justify-center">
|
||||
<img class="max-w-128 w-auto max-h-128 w-auto" src={URL.createObjectURL(imageArguments.blob)} />
|
||||
<img
|
||||
class="max-w-128 max-h-128 w-auto w-auto"
|
||||
src={URL.createObjectURL(imageArguments.blob)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
|
@ -54,8 +59,7 @@
|
|||
<Tr t={t.general.back} />
|
||||
</BackButton>
|
||||
<button on:click={() => del()} class="primary w-full">
|
||||
|
||||
<TrashIcon class="w-8 m-1" />
|
||||
<TrashIcon class="m-1 w-8" />
|
||||
<Tr t={t.imageQueue.confirmDelete} />
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -23,13 +23,13 @@
|
|||
<Tr t={q.intro} />
|
||||
</div>
|
||||
|
||||
<UploadingImageCounter {state}/>
|
||||
<UploadingImageCounter {state} />
|
||||
|
||||
{#if $isUploading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<button class="primary" on:click={() => state.imageUploadManager.uploadQueue()}>
|
||||
<ArrowPathIcon class="w-8 h-8 m-1" />
|
||||
<ArrowPathIcon class="m-1 h-8 w-8" />
|
||||
<Tr t={q.retryAll} />
|
||||
</button>
|
||||
{/if}
|
||||
|
|
|
@ -47,7 +47,14 @@
|
|||
errs.push(canBeUploaded.error)
|
||||
continue
|
||||
}
|
||||
await state?.imageUploadManager?.uploadImageAndApply(file, tags, targetKey, noBlur, feature, { ignoreGPS })
|
||||
await state?.imageUploadManager?.uploadImageAndApply(
|
||||
file,
|
||||
tags,
|
||||
targetKey,
|
||||
noBlur,
|
||||
feature,
|
||||
{ ignoreGPS }
|
||||
)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
state.reportError(e, "Could not upload image")
|
||||
|
|
|
@ -26,9 +26,9 @@
|
|||
*/
|
||||
function getCount(input: Store<string[]>): Store<number> {
|
||||
if (featureId == "*") {
|
||||
return input.map(inp => inp.length)
|
||||
return input.map((inp) => inp.length)
|
||||
}
|
||||
return input.map(success => success.filter(item => item === featureId).length)
|
||||
return input.map((success) => success.filter((item) => item === featureId).length)
|
||||
}
|
||||
|
||||
let successfull = getCount(state.imageUploadManager.successfull)
|
||||
|
@ -39,7 +39,7 @@
|
|||
const t = Translations.t.image
|
||||
const debugging = state.featureSwitches.featureSwitchIsDebugging
|
||||
let dismissed = 0
|
||||
failed.addCallbackAndRun(failed => {
|
||||
failed.addCallbackAndRun((failed) => {
|
||||
dismissed = Math.min(failed, dismissed)
|
||||
})
|
||||
</script>
|
||||
|
@ -56,7 +56,7 @@
|
|||
{#if $pending - $failed === 1}
|
||||
<Tr t={t.upload.one.uploading} />
|
||||
{:else if $pending - $failed > 1}
|
||||
<Tr t={t.upload.multiple.uploading.Subs({count: $pending})} />
|
||||
<Tr t={t.upload.multiple.uploading.Subs({ count: $pending })} />
|
||||
{/if}
|
||||
</Loading>
|
||||
</div>
|
||||
|
@ -70,6 +70,6 @@
|
|||
{#if $successfull === 1}
|
||||
<Tr cls="thanks" t={t.upload.one.done} />
|
||||
{:else if $successfull > 1}
|
||||
<Tr cls="thanks" t={t.upload.multiple.done.Subs({count: $successfull})} />
|
||||
<Tr cls="thanks" t={t.upload.multiple.done.Subs({ count: $successfull })} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
|
|
@ -4,38 +4,37 @@ import { Feature, Geometry, Point } from "geojson"
|
|||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import { HotspotProperties, PanoramaView } from "../../Logic/ImageProviders/ImageProvider"
|
||||
|
||||
|
||||
export class PhotoSphereViewerWrapper {
|
||||
|
||||
private imageInfo: Feature<Point, PanoramaView>
|
||||
private readonly viewer: Pannellum.Viewer
|
||||
private nearbyFeatures: Feature<Geometry, HotspotProperties>[] = []
|
||||
|
||||
constructor(container: HTMLElement, imageInfo: Feature<Point, PanoramaView>, nearbyFeatures?: Feature<Geometry, HotspotProperties>[]) {
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
imageInfo: Feature<Point, PanoramaView>,
|
||||
nearbyFeatures?: Feature<Geometry, HotspotProperties>[]
|
||||
) {
|
||||
this.imageInfo = imageInfo
|
||||
this.viewer = pannellum.viewer(container,
|
||||
<any>{
|
||||
default: {
|
||||
firstScene: imageInfo.properties.url,
|
||||
sceneFadeDuration: 250
|
||||
this.viewer = pannellum.viewer(container, <any>{
|
||||
default: {
|
||||
firstScene: imageInfo.properties.url,
|
||||
sceneFadeDuration: 250,
|
||||
},
|
||||
scenes: {
|
||||
[imageInfo.properties.url]: {
|
||||
type: "equirectangular",
|
||||
hfov: 110,
|
||||
panorama: imageInfo.properties.url,
|
||||
autoLoad: true,
|
||||
hotSpots: [],
|
||||
sceneFadeDuration: 250,
|
||||
compass: true,
|
||||
showControls: false,
|
||||
northOffset: imageInfo.properties.northOffset,
|
||||
horizonPitch: imageInfo.properties.pitchOffset,
|
||||
},
|
||||
scenes: {
|
||||
[imageInfo.properties.url]:
|
||||
{
|
||||
type: "equirectangular",
|
||||
hfov: 110,
|
||||
panorama: imageInfo.properties.url,
|
||||
autoLoad: true,
|
||||
hotSpots: [],
|
||||
sceneFadeDuration: 250,
|
||||
compass: true,
|
||||
showControls: false,
|
||||
northOffset: imageInfo.properties.northOffset,
|
||||
horizonPitch: imageInfo.properties.pitchOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
this.setNearbyFeatures(nearbyFeatures)
|
||||
}
|
||||
|
@ -43,12 +42,13 @@ export class PhotoSphereViewerWrapper {
|
|||
public calculatePitch(feature: Feature): number {
|
||||
const coors = this.imageInfo.geometry.coordinates
|
||||
const distance = GeoOperations.distanceBetween(
|
||||
<[number, number]>coors, GeoOperations.centerpointCoordinates(feature)
|
||||
<[number, number]>coors,
|
||||
GeoOperations.centerpointCoordinates(feature)
|
||||
)
|
||||
|
||||
// In: -pi/2 up to pi/2
|
||||
const alpha = Math.atan(distance / 4) // in radians
|
||||
const degrees = alpha * 360 / (2 * Math.PI)
|
||||
const degrees = (alpha * 360) / (2 * Math.PI)
|
||||
return -degrees
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ export class PhotoSphereViewerWrapper {
|
|||
this.viewer.addScene(imageInfo.properties.url, <any>{
|
||||
panorama: imageInfo.properties.url,
|
||||
northOffset: imageInfo.properties.northOffset,
|
||||
type: "equirectangular"
|
||||
type: "equirectangular",
|
||||
})
|
||||
|
||||
this.viewer.loadScene(imageInfo.properties.url, 0, imageInfo.properties.northOffset)
|
||||
|
@ -70,7 +70,8 @@ export class PhotoSphereViewerWrapper {
|
|||
}
|
||||
|
||||
private clearHotspots() {
|
||||
const hotspots = this.viewer.getConfig()["scenes"][this.imageInfo.properties.url].hotSpots ?? []
|
||||
const hotspots =
|
||||
this.viewer.getConfig()["scenes"][this.imageInfo.properties.url].hotSpots ?? []
|
||||
for (const hotspot of hotspots) {
|
||||
this.viewer.removeHotSpot(hotspot?.id, this.imageInfo.properties.url)
|
||||
}
|
||||
|
@ -95,20 +96,21 @@ export class PhotoSphereViewerWrapper {
|
|||
} else if (!isNaN(f.properties.pitch)) {
|
||||
pitch = f.properties.pitch
|
||||
}
|
||||
this.viewer.addHotSpot({
|
||||
type: f.properties.gotoPanorama !== undefined ? "scene" : "info",
|
||||
yaw: (yaw - northOffs) % 360,
|
||||
pitch,
|
||||
text: f.properties.name,
|
||||
clickHandlerFunc: () => {
|
||||
this.setPanorama(f.properties.gotoPanorama)
|
||||
}
|
||||
}, this.imageInfo.properties.url)
|
||||
this.viewer.addHotSpot(
|
||||
{
|
||||
type: f.properties.gotoPanorama !== undefined ? "scene" : "info",
|
||||
yaw: (yaw - northOffs) % 360,
|
||||
pitch,
|
||||
text: f.properties.name,
|
||||
clickHandlerFunc: () => {
|
||||
this.setPanorama(f.properties.gotoPanorama)
|
||||
},
|
||||
},
|
||||
this.imageInfo.properties.url
|
||||
)
|
||||
if (f.properties.focus) {
|
||||
this.viewer.setYaw(yaw - northOffs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
/**
|
||||
* Used to quickly calculate a distance by dragging a map (and selecting start- and endpoints)
|
||||
*/
|
||||
|
@ -24,71 +23,77 @@
|
|||
|
||||
export let value: UIEventSource<number>
|
||||
export let feature: Feature
|
||||
export let args: { background?: string, zoom?: number }
|
||||
export let args: { background?: string; zoom?: number }
|
||||
export let state: ThemeViewState = undefined
|
||||
export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
|
||||
|
||||
let center = GeoOperations.centerpointCoordinates(feature)
|
||||
export let initialCoordinate: { lon: number, lat: number } = { lon: center[0], lat: center[1] }
|
||||
let mapLocation: UIEventSource<{ lon: number, lat: number }> = new UIEventSource(initialCoordinate)
|
||||
export let initialCoordinate: { lon: number; lat: number } = { lon: center[0], lat: center[1] }
|
||||
let mapLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(
|
||||
initialCoordinate
|
||||
)
|
||||
let bg = args?.background
|
||||
let rasterLayer = state?.mapProperties.rasterLayer
|
||||
if (bg !== undefined) {
|
||||
if (eliCategory.indexOf(bg) >= 0) {
|
||||
const availableLayers = state.availableLayers.store.data
|
||||
const startLayer: RasterLayerPolygon = RasterLayerUtils.SelectBestLayerAccordingTo(availableLayers, bg)
|
||||
const startLayer: RasterLayerPolygon = RasterLayerUtils.SelectBestLayerAccordingTo(
|
||||
availableLayers,
|
||||
bg
|
||||
)
|
||||
rasterLayer = new UIEventSource(startLayer)
|
||||
state?.mapProperties.rasterLayer.addCallbackD(layer => rasterLayer.set(layer))
|
||||
state?.mapProperties.rasterLayer.addCallbackD((layer) => rasterLayer.set(layer))
|
||||
}
|
||||
|
||||
}
|
||||
let mapProperties: Partial<MapProperties> = {
|
||||
rasterLayer: rasterLayer,
|
||||
location: mapLocation,
|
||||
zoom: new UIEventSource(args?.zoom ?? 18)
|
||||
zoom: new UIEventSource(args?.zoom ?? 18),
|
||||
}
|
||||
|
||||
let start: UIEventSource<{ lon: number, lat: number }> = new UIEventSource(undefined)
|
||||
let start: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined)
|
||||
|
||||
function selectStart() {
|
||||
start.set(mapLocation.data)
|
||||
}
|
||||
|
||||
let lengthFeature: Store<Feature[]> = start.map(start => {
|
||||
if (!start) {
|
||||
return []
|
||||
}
|
||||
// A bit of a double task: calculate the actual value _and_ the map rendering
|
||||
const end = mapLocation.data
|
||||
const distance = GeoOperations.distanceBetween([start.lon, start.lat], [end.lon, end.lat])
|
||||
value.set(distance.toFixed(2))
|
||||
|
||||
|
||||
return <Feature[]>[
|
||||
|
||||
{
|
||||
type: "Feature",
|
||||
properties: {
|
||||
id: "distance_line_" + distance,
|
||||
distance: "" + distance
|
||||
},
|
||||
geometry: {
|
||||
type: "LineString",
|
||||
coordinates: [[start.lon, start.lat], [end.lon, end.lat]]
|
||||
}
|
||||
let lengthFeature: Store<Feature[]> = start.map(
|
||||
(start) => {
|
||||
if (!start) {
|
||||
return []
|
||||
}
|
||||
]
|
||||
// A bit of a double task: calculate the actual value _and_ the map rendering
|
||||
const end = mapLocation.data
|
||||
const distance = GeoOperations.distanceBetween([start.lon, start.lat], [end.lon, end.lat])
|
||||
value.set(distance.toFixed(2))
|
||||
|
||||
}, [mapLocation])
|
||||
return <Feature[]>[
|
||||
{
|
||||
type: "Feature",
|
||||
properties: {
|
||||
id: "distance_line_" + distance,
|
||||
distance: "" + distance,
|
||||
},
|
||||
geometry: {
|
||||
type: "LineString",
|
||||
coordinates: [
|
||||
[start.lon, start.lat],
|
||||
[end.lon, end.lat],
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
[mapLocation]
|
||||
)
|
||||
|
||||
new ShowDataLayer(map, {
|
||||
layer: new LayerConfig(conflation),
|
||||
features: new StaticFeatureSource(lengthFeature)
|
||||
features: new StaticFeatureSource(lengthFeature),
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<div class="relative w-full h-64">
|
||||
<div class="relative h-64 w-full">
|
||||
<LocationInput value={mapLocation} {mapProperties} {map} />
|
||||
<div class="absolute bottom-0 left-0 p-4">
|
||||
<OpenBackgroundSelectorButton {state} {map} />
|
||||
|
|
|
@ -61,7 +61,7 @@ export default class Validators {
|
|||
"translation",
|
||||
"url",
|
||||
"velopark",
|
||||
"wikidata"
|
||||
"wikidata",
|
||||
] as const
|
||||
|
||||
public static readonly AllValidators: ReadonlyArray<Validator> = [
|
||||
|
@ -93,7 +93,7 @@ export default class Validators {
|
|||
new VeloparkValidator(),
|
||||
new NameSuggestionIndexValidator(),
|
||||
new CurrencyValidator(),
|
||||
new RegexValidator()
|
||||
new RegexValidator(),
|
||||
]
|
||||
|
||||
private static _byType = Validators._byTypeConstructor()
|
||||
|
|
|
@ -6,17 +6,19 @@ export default class DistanceValidator extends Validator {
|
|||
private readonly docs: string = [
|
||||
"#### Helper-arguments",
|
||||
"Options are:",
|
||||
["````json",
|
||||
" \"background\": \"some_background_id or category, e.g. 'map'\"",
|
||||
" \"zoom\": 20 # initial zoom level of the map",
|
||||
[
|
||||
"````json",
|
||||
' "background": "some_background_id or category, e.g. \'map\'"',
|
||||
' "zoom": 20 # initial zoom level of the map',
|
||||
"}",
|
||||
"```"].join("\n")
|
||||
"```",
|
||||
].join("\n"),
|
||||
].join("\n\n")
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
"distance",
|
||||
"A geographical distance in meters (rounded at two points). Will give an extra minimap with a measurement tool. Arguments: [ zoomlevel, preferredBackgroundMapType (comma separated) ], e.g. `[\"21\", \"map,photo\"]",
|
||||
'A geographical distance in meters (rounded at two points). Will give an extra minimap with a measurement tool. Arguments: [ zoomlevel, preferredBackgroundMapType (comma separated) ], e.g. `["21", "map,photo"]',
|
||||
"decimal"
|
||||
)
|
||||
}
|
||||
|
@ -35,14 +37,18 @@ export default class DistanceValidator extends Validator {
|
|||
}
|
||||
|
||||
const optionalKeys = ["background", "zoom"]
|
||||
const keys = Object.keys(args).filter(k => optionalKeys.indexOf(k) < 0)
|
||||
const keys = Object.keys(args).filter((k) => optionalKeys.indexOf(k) < 0)
|
||||
if (keys.length > 0) {
|
||||
return "Unknown key " + keys.join("; ") + "; use " + optionalKeys.join("; ") + " instead"
|
||||
return (
|
||||
"Unknown key " + keys.join("; ") + "; use " + optionalKeys.join("; ") + " instead"
|
||||
)
|
||||
}
|
||||
const bg = args["background"]
|
||||
if (bg && eliCategory.indexOf(bg) < 0) {
|
||||
return "The given background layer is not a recognized ELI-type. Perhaps you meant one of " +
|
||||
Utils.sortedByLevenshteinDistance(bg, eliCategory, x => x).slice(0, 5)
|
||||
return (
|
||||
"The given background layer is not a recognized ELI-type. Perhaps you meant one of " +
|
||||
Utils.sortedByLevenshteinDistance(bg, eliCategory, (x) => x).slice(0, 5)
|
||||
)
|
||||
}
|
||||
if (typeof args["zoom"] !== "number") {
|
||||
return "zoom must be a number, got a " + typeof args["zoom"]
|
||||
|
|
|
@ -302,9 +302,7 @@
|
|||
</div>
|
||||
</NextButton>
|
||||
</div>
|
||||
<TagHint
|
||||
tags={selectedPreset.preset.tags}
|
||||
/>
|
||||
<TagHint tags={selectedPreset.preset.tags} />
|
||||
</TitledPanel>
|
||||
{:else if _globalFilter?.length > 0 && _globalFilter?.length > checkedOfGlobalFilters}
|
||||
<Tr t={_globalFilter[checkedOfGlobalFilters].onNewPoint?.safetyCheck} cls="mx-12" />
|
||||
|
|
|
@ -48,7 +48,9 @@
|
|||
|
||||
let selectedTags: UploadableTag[]
|
||||
let changedProperties = undefined
|
||||
$: changedProperties = TagUtils.changeAsProperties(And.construct(selectedTags)?.asChange(tags?.data ?? {}) ?? [])
|
||||
$: changedProperties = TagUtils.changeAsProperties(
|
||||
And.construct(selectedTags)?.asChange(tags?.data ?? {}) ?? []
|
||||
)
|
||||
let isHardDelete = undefined
|
||||
$: isHardDelete = changedProperties[DeleteConfig.deleteReasonKey] !== undefined
|
||||
|
||||
|
@ -58,7 +60,9 @@
|
|||
}
|
||||
currentState = "applying"
|
||||
let actionToTake: OsmChangeAction
|
||||
const changedProperties = TagUtils.changeAsProperties(And.construct(selectedTags)?.asChange(tags?.data ?? {}))
|
||||
const changedProperties = TagUtils.changeAsProperties(
|
||||
And.construct(selectedTags)?.asChange(tags?.data ?? {})
|
||||
)
|
||||
const deleteReason = changedProperties[DeleteConfig.deleteReasonKey]
|
||||
if (deleteReason) {
|
||||
let softDeletionTags: UploadableTag
|
||||
|
|
|
@ -37,21 +37,19 @@
|
|||
|
||||
{#if tagRenderings.length > 0}
|
||||
<div class="mb-8">
|
||||
|
||||
<AccordionSingle>
|
||||
<div slot="header">
|
||||
{#if headerTr}
|
||||
<TagRenderingAnswer {tags} {layer} config={headerTr} {state} {selectedElement} />
|
||||
{:else}
|
||||
{header}
|
||||
{/if}
|
||||
</div>
|
||||
<div slot="header">
|
||||
{#if headerTr}
|
||||
<TagRenderingAnswer {tags} {layer} config={headerTr} {state} {selectedElement} />
|
||||
{:else}
|
||||
{header}
|
||||
{/if}
|
||||
</div>
|
||||
{#each tagRenderings as config (config.id)}
|
||||
{#if config.IsKnown($tags) && (config.condition === undefined || config.condition.matchesProperties($tags))}
|
||||
<TagRenderingEditableDynamic {tags} {config} {state} {selectedElement} {layer} />
|
||||
{/if}
|
||||
{/each}
|
||||
</AccordionSingle>
|
||||
{#if config.IsKnown($tags) && (config.condition === undefined || config.condition.matchesProperties($tags))}
|
||||
<TagRenderingEditableDynamic {tags} {config} {state} {selectedElement} {layer} />
|
||||
{/if}
|
||||
{/each}
|
||||
</AccordionSingle>
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
|
|
|
@ -119,6 +119,7 @@
|
|||
}, 50)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $loginEnabled}
|
||||
<div
|
||||
bind:this={questionboxElem}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
if (config === undefined) {
|
||||
console.error("TagRenderingAnswer: Config is undefined")
|
||||
throw ("Config is undefined in tagRenderingAnswer")
|
||||
throw "Config is undefined in tagRenderingAnswer"
|
||||
}
|
||||
let trs: Store<{ then: Translation; icon?: string; iconClass?: string }[]> = tags.mapD((tags) =>
|
||||
Utils.NoNull(config?.GetRenderValues(tags))
|
||||
|
|
|
@ -23,11 +23,21 @@
|
|||
export let getCountry = () => "?"
|
||||
|
||||
onMount(() => {
|
||||
console.log("Setting selected unit based on country", getCountry(), "and upstream value:", upstreamValue.data)
|
||||
console.log(
|
||||
"Setting selected unit based on country",
|
||||
getCountry(),
|
||||
"and upstream value:",
|
||||
upstreamValue.data
|
||||
)
|
||||
if (upstreamValue.data === undefined || upstreamValue.data === "") {
|
||||
// Init the selected unit
|
||||
let denomination: Denomination = unit.getDefaultDenomination(getCountry)
|
||||
console.log("Found denom", denomination.canonical, "available denominations are:", unit.denominations.map(denom => denom.canonical))
|
||||
console.log(
|
||||
"Found denom",
|
||||
denomination.canonical,
|
||||
"available denominations are:",
|
||||
unit.denominations.map((denom) => denom.canonical)
|
||||
)
|
||||
selectedUnit.setData(denomination.canonical)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -16,17 +16,16 @@
|
|||
export let state: SpecialVisualizationState
|
||||
let searchTerm = state.searchState.searchTerm
|
||||
let recentThemes = state.userRelatedState.recentlyVisitedThemes.value.map((themes) => {
|
||||
const recent = themes.filter((th) => th !== state.theme.id).slice(0, 6)
|
||||
const deduped: MinimalThemeInformation[] = []
|
||||
for (const theme of recent) {
|
||||
if (deduped.some(th => th.id === theme.id)) {
|
||||
continue
|
||||
}
|
||||
deduped.push(theme)
|
||||
const recent = themes.filter((th) => th !== state.theme.id).slice(0, 6)
|
||||
const deduped: MinimalThemeInformation[] = []
|
||||
for (const theme of recent) {
|
||||
if (deduped.some((th) => th.id === theme.id)) {
|
||||
continue
|
||||
}
|
||||
return deduped
|
||||
deduped.push(theme)
|
||||
}
|
||||
)
|
||||
return deduped
|
||||
})
|
||||
let themeResults = state.searchState.themeSuggestions
|
||||
|
||||
const t = Translations.t.general.search
|
||||
|
|
|
@ -79,7 +79,13 @@ export class ImageVisualisations {
|
|||
const estimated = tags.mapD((tags) =>
|
||||
AllImageProviders.estimateNumberOfImages(tags, imagePrefixes)
|
||||
)
|
||||
return new SvelteUIElement(ImageCarousel, { state, tags, images, estimated, feature })
|
||||
return new SvelteUIElement(ImageCarousel, {
|
||||
state,
|
||||
tags,
|
||||
images,
|
||||
estimated,
|
||||
feature,
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import {
|
||||
SpecialVisualization,
|
||||
SpecialVisualizationState,
|
||||
SpecialVisualizationSvelte,
|
||||
} from "../SpecialVisualization"
|
||||
import Constants from "../../Models/Constants"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Feature } from "geojson"
|
||||
|
|
|
@ -80,23 +80,22 @@ export class SettingsVisualisations {
|
|||
group: "settings",
|
||||
docs: "Shows the current state of storage",
|
||||
args: [],
|
||||
constr: function(state: SpecialVisualizationState): SvelteUIElement {
|
||||
constr: function (state: SpecialVisualizationState): SvelteUIElement {
|
||||
const data = {}
|
||||
for (const key in localStorage) {
|
||||
data[key] = localStorage[key]
|
||||
}
|
||||
const tags = new UIEventSource(data)
|
||||
|
||||
navigator.storage.estimate().then(estimate => {
|
||||
navigator.storage.estimate().then((estimate) => {
|
||||
data["__usage:current:bytes"] = estimate.usage
|
||||
data["__usage:current:human"] = Utils.toHumanByteSize(estimate.usage)
|
||||
data["__usage:quota:bytes"] = estimate.quota
|
||||
data["__usage:quota:human"] = Utils.toHumanByteSize(estimate.quota)
|
||||
tags.ping()
|
||||
|
||||
})
|
||||
return new SvelteUIElement(AllTagsPanel, { state, tags })
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "clear_caches",
|
||||
|
|
|
@ -29,13 +29,13 @@ class QuestionViz implements SpecialVisualizationSvelte {
|
|||
},
|
||||
{
|
||||
name: "blacklisted-labels",
|
||||
doc: "One or more ';'-separated labels of questions which should _not_ be included. Note that the questionbox which is added by default will blacklist 'hidden'"
|
||||
doc: "One or more ';'-separated labels of questions which should _not_ be included. Note that the questionbox which is added by default will blacklist 'hidden'",
|
||||
},
|
||||
{
|
||||
name: "show_all",
|
||||
default: "user-preference",
|
||||
doc: "Either `no`, `yes` or `user-preference`. Indicates if all questions should be shown at once"
|
||||
}
|
||||
doc: "Either `no`, `yes` or `user-preference`. Indicates if all questions should be shown at once",
|
||||
},
|
||||
]
|
||||
svelteBased = true
|
||||
group: "default"
|
||||
|
@ -69,7 +69,7 @@ class QuestionViz implements SpecialVisualizationSvelte {
|
|||
state,
|
||||
onlyForLabels: labels,
|
||||
notForLabels: blacklist,
|
||||
showAllQuestionsAtOnce
|
||||
showAllQuestionsAtOnce,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,8 +24,8 @@ export class WebAndCommunicationSpecialVisualisations {
|
|||
{
|
||||
name: "key",
|
||||
doc: "The attribute-name containing the link",
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
|
||||
constr(
|
||||
|
@ -35,7 +35,7 @@ export class WebAndCommunicationSpecialVisualisations {
|
|||
): BaseUIElement {
|
||||
const key = argument[0]
|
||||
return new SvelteUIElement(FediverseLink, { key, tags, state })
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "wikipedia",
|
||||
|
@ -45,8 +45,8 @@ export class WebAndCommunicationSpecialVisualisations {
|
|||
{
|
||||
name: "keyToShowWikipediaFor",
|
||||
doc: "Use the wikidata entry from this key to show the wikipedia article for. Multiple keys can be given (separated by ';'), in which case the first matching value is used",
|
||||
defaultValue: "wikidata;wikipedia"
|
||||
}
|
||||
defaultValue: "wikidata;wikipedia",
|
||||
},
|
||||
],
|
||||
needsUrls: [...Wikidata.neededUrls, ...Wikipedia.neededUrls],
|
||||
|
||||
|
@ -59,9 +59,9 @@ export class WebAndCommunicationSpecialVisualisations {
|
|||
return tags[key]?.split(";")?.map((id) => id.trim()) ?? []
|
||||
})
|
||||
return new SvelteUIElement(WikipediaPanel, {
|
||||
wikiIds
|
||||
wikiIds,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "wikidata_label",
|
||||
|
@ -72,8 +72,8 @@ export class WebAndCommunicationSpecialVisualisations {
|
|||
{
|
||||
name: "keyToShowWikidataFor",
|
||||
doc: "Use the wikidata entry from this key to show the label",
|
||||
defaultValue: "wikidata"
|
||||
}
|
||||
defaultValue: "wikidata",
|
||||
},
|
||||
],
|
||||
needsUrls: Wikidata.neededUrls,
|
||||
example:
|
||||
|
@ -87,7 +87,7 @@ export class WebAndCommunicationSpecialVisualisations {
|
|||
)
|
||||
return wikidataIds?.[0]
|
||||
})
|
||||
const entry = id.bind(id => Wikidata.LoadWikidataEntry(id))
|
||||
const entry = id.bind((id) => Wikidata.LoadWikidataEntry(id))
|
||||
|
||||
return new VariableUiElement(
|
||||
entry.map((e) => {
|
||||
|
@ -96,8 +96,9 @@ export class WebAndCommunicationSpecialVisualisations {
|
|||
}
|
||||
const response = <WikidataResponse>e["success"]
|
||||
return Translation.fromMap(response.labels)
|
||||
}))
|
||||
}
|
||||
})
|
||||
)
|
||||
},
|
||||
},
|
||||
new MapillaryLinkVis(),
|
||||
{
|
||||
|
@ -108,29 +109,29 @@ export class WebAndCommunicationSpecialVisualisations {
|
|||
{
|
||||
name: "to",
|
||||
doc: "Who to send the email to?",
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "subject",
|
||||
doc: "The subject of the email",
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "body",
|
||||
doc: "The text in the email",
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "button_text",
|
||||
doc: "The text shown on the button in the UI",
|
||||
required: true
|
||||
}
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
|
||||
constr(__, tags, args) {
|
||||
return new SvelteUIElement(SendEmail, { args, tags })
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
funcName: "link",
|
||||
|
@ -140,29 +141,29 @@ export class WebAndCommunicationSpecialVisualisations {
|
|||
{
|
||||
name: "text",
|
||||
doc: "Text to be shown",
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "href",
|
||||
doc: "The URL to link to. Note that this will be URI-encoded before ",
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "class",
|
||||
doc: "CSS-classes to add to the element"
|
||||
doc: "CSS-classes to add to the element",
|
||||
},
|
||||
{
|
||||
name: "download",
|
||||
doc: "Expects a string which denotes the filename to download the contents of `href` into. If set, this link will act as a download-button."
|
||||
doc: "Expects a string which denotes the filename to download the contents of `href` into. If set, this link will act as a download-button.",
|
||||
},
|
||||
{
|
||||
name: "arialabel",
|
||||
doc: "If set, this text will be used as aria-label"
|
||||
doc: "If set, this text will be used as aria-label",
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
doc: "If set, show this icon next to the link. You might want to combine this with `class: button`"
|
||||
}
|
||||
doc: "If set, show this icon next to the link. You might want to combine this with `class: button`",
|
||||
},
|
||||
],
|
||||
|
||||
constr(
|
||||
|
@ -184,10 +185,10 @@ export class WebAndCommunicationSpecialVisualisations {
|
|||
download: tagSource.map((tags) => Utils.SubstituteKeys(download, tags)),
|
||||
ariaLabel: tagSource.map((tags) => Utils.SubstituteKeys(ariaLabel, tags)),
|
||||
newTab: new ImmutableStore(newTab),
|
||||
icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags))
|
||||
icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags)),
|
||||
}).setSpan()
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { FixedUiElement } from "./Base/FixedUiElement"
|
||||
import BaseUIElement from "./BaseUIElement"
|
||||
import { default as FeatureTitle } from "./Popup/Title.svelte"
|
||||
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"
|
||||
import {
|
||||
RenderingSpecification,
|
||||
SpecialVisualization,
|
||||
SpecialVisualizationState,
|
||||
} from "./SpecialVisualization"
|
||||
import { HistogramViz } from "./Popup/HistogramViz"
|
||||
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
|
||||
import { MultiApplyViz } from "./Popup/MultiApplyViz"
|
||||
|
@ -36,11 +40,8 @@ import { UISpecialVisualisations } from "./SpecialVisualisations/UISpecialVisual
|
|||
import { SettingsVisualisations } from "./SpecialVisualisations/SettingsVisualisations"
|
||||
import { ReviewSpecialVisualisations } from "./SpecialVisualisations/ReviewSpecialVisualisations"
|
||||
import { DataImportSpecialVisualisations } from "./SpecialVisualisations/DataImportSpecialVisualisations"
|
||||
import TagrenderingManipulationSpecialVisualisations
|
||||
from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations"
|
||||
import {
|
||||
WebAndCommunicationSpecialVisualisations
|
||||
} from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations"
|
||||
import TagrenderingManipulationSpecialVisualisations from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations"
|
||||
import { WebAndCommunicationSpecialVisualisations } from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations"
|
||||
import ClearGPSHistory from "./BigComponents/ClearGPSHistory.svelte"
|
||||
import AllFeaturesStatistics from "./Statistics/AllFeaturesStatistics.svelte"
|
||||
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
export let layer: LayerConfig
|
||||
export let state: ThemeViewState
|
||||
let bbox = state.mapProperties.bounds
|
||||
let elements: Store<Feature[]> = bbox.mapD(bbox => state.perLayer.get(layer.id).GetFeaturesWithin(bbox))
|
||||
let elements: Store<Feature[]> = bbox.mapD((bbox) =>
|
||||
state.perLayer.get(layer.id).GetFeaturesWithin(bbox)
|
||||
)
|
||||
|
||||
let trs = layer.tagRenderings.filter((tr) => tr.question)
|
||||
</script>
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
return "offline"
|
||||
}
|
||||
}),
|
||||
message: osmApi
|
||||
message: osmApi,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@
|
|||
}
|
||||
const files: string[] = s["success"]["allFiles"]
|
||||
return "Contains " + (files.length ?? "no") + " files"
|
||||
})
|
||||
}),
|
||||
})
|
||||
}
|
||||
{
|
||||
|
@ -107,7 +107,7 @@
|
|||
return "degraded"
|
||||
}
|
||||
}),
|
||||
message: simpleMessage(testDownload(Constants.panoramax.url + "/api"))
|
||||
message: simpleMessage(testDownload(Constants.panoramax.url + "/api")),
|
||||
})
|
||||
}
|
||||
{
|
||||
|
@ -123,7 +123,7 @@
|
|||
return "degraded"
|
||||
}
|
||||
}),
|
||||
message: simpleMessage(testDownload(Constants.GeoIpServer + "/ip"))
|
||||
message: simpleMessage(testDownload(Constants.GeoIpServer + "/ip")),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,7 @@
|
|||
}
|
||||
return "degraded"
|
||||
}),
|
||||
message: simpleMessage(status)
|
||||
message: simpleMessage(status),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -161,7 +161,7 @@
|
|||
}
|
||||
return "online"
|
||||
}),
|
||||
message: simpleMessage(status)
|
||||
message: simpleMessage(status),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -200,7 +200,7 @@
|
|||
|
||||
const json = JSON.stringify(s["success"], null, " ")
|
||||
return "Database is " + Math.floor(timediffDays) + " days out of sync\n\n" + json
|
||||
})
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -213,7 +213,7 @@
|
|||
layer: "food",
|
||||
z: 14,
|
||||
x: 8848,
|
||||
y: 5828
|
||||
y: 5828,
|
||||
})
|
||||
)
|
||||
services.push({
|
||||
|
@ -224,7 +224,7 @@
|
|||
}
|
||||
return "online"
|
||||
}),
|
||||
message: new ImmutableStore("See SettingUpPSQL.md to fix")
|
||||
message: new ImmutableStore("See SettingUpPSQL.md to fix"),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -243,7 +243,7 @@
|
|||
}
|
||||
return "degraded"
|
||||
}),
|
||||
message: status.map((s) => JSON.stringify(s))
|
||||
message: status.map((s) => JSON.stringify(s)),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -262,7 +262,7 @@
|
|||
return "online"
|
||||
}
|
||||
return "degraded"
|
||||
})
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -281,7 +281,7 @@
|
|||
}
|
||||
return "degraded"
|
||||
}),
|
||||
message: simpleMessage(status)
|
||||
message: simpleMessage(status),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -307,7 +307,7 @@
|
|||
|
||||
return "online"
|
||||
}),
|
||||
message: simpleMessage(status)
|
||||
message: simpleMessage(status),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -320,7 +320,7 @@
|
|||
return "online"
|
||||
}
|
||||
return "offline"
|
||||
})
|
||||
}),
|
||||
})
|
||||
|
||||
services.push({
|
||||
|
@ -331,35 +331,33 @@
|
|||
}
|
||||
// This code will break in the future. Time to blame past me!
|
||||
const response = JSON.parse(r["error"].substring("other error: , ".length))
|
||||
if (response.message === "\"images\" is required") {
|
||||
if (response.message === '"images" is required') {
|
||||
// Actual expected behaviour
|
||||
return "online"
|
||||
}
|
||||
console.log("R", response)
|
||||
return "offline"
|
||||
})
|
||||
}),
|
||||
})
|
||||
}
|
||||
{
|
||||
services.push({
|
||||
name: "Version Control Server (Forgéjo)",
|
||||
status: testDownload("https://source.mapcomplete.org", true).mapD(r => {
|
||||
status: testDownload("https://source.mapcomplete.org", true).mapD((r) => {
|
||||
if (r["success"]) {
|
||||
return "online"
|
||||
}
|
||||
return "offline"
|
||||
|
||||
})
|
||||
}),
|
||||
})
|
||||
services.push({
|
||||
name: "Translation service (Weblate)",
|
||||
status: testDownload("https://translate.mapcomplete.org", true).mapD(r => {
|
||||
status: testDownload("https://translate.mapcomplete.org", true).mapD((r) => {
|
||||
if (r["success"]) {
|
||||
return "online"
|
||||
}
|
||||
return "offline"
|
||||
|
||||
})
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -433,7 +431,6 @@
|
|||
let now = Math.round(new Date().getTime() / 1000)
|
||||
let twoDaysAgo = now - 2 * 24 * 60 * 60
|
||||
let lastHour = now - 60 * 60
|
||||
|
||||
</script>
|
||||
|
||||
<h1>MapComplete status indicators</h1>
|
||||
|
@ -457,16 +454,21 @@
|
|||
|
||||
<h3>Panoramax & OSM.fr Blurring service</h3>
|
||||
<a href="https://status.thibaultmol.link/status/panoramax">Panoramax.MapComplete.org status page</a>
|
||||
<a href="https://munin.openstreetmap.fr/osm37.openstreetmap.fr/blur.vm.openstreetmap.fr/index.html#system"
|
||||
target="_blank" rel="noopener">
|
||||
<a
|
||||
href="https://munin.openstreetmap.fr/osm37.openstreetmap.fr/blur.vm.openstreetmap.fr/index.html#system"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
See more statistics for the blurring service
|
||||
</a>
|
||||
<img
|
||||
style="width: 80rem"
|
||||
src={`https://munin.openstreetmap.fr/munin-cgi/munin-cgi-graph/osm37.openstreetmap.fr/blur.vm.openstreetmap.fr/nvidia_gpu_power-pinpoint=${twoDaysAgo},${now}.png?&lower_limit=&upper_limit=&size_x=800&size_y=400`} />
|
||||
src={`https://munin.openstreetmap.fr/munin-cgi/munin-cgi-graph/osm37.openstreetmap.fr/blur.vm.openstreetmap.fr/nvidia_gpu_power-pinpoint=${twoDaysAgo},${now}.png?&lower_limit=&upper_limit=&size_x=800&size_y=400`}
|
||||
/>
|
||||
<img
|
||||
style="width: 80rem"
|
||||
src={`https://munin.openstreetmap.fr/munin-cgi/munin-cgi-graph/osm37.openstreetmap.fr/blur.vm.openstreetmap.fr/nvidia_gpu_power-pinpoint=${lastHour},${now}.png?&lower_limit=&upper_limit=&size_x=800&size_y=400`} />
|
||||
src={`https://munin.openstreetmap.fr/munin-cgi/munin-cgi-graph/osm37.openstreetmap.fr/blur.vm.openstreetmap.fr/nvidia_gpu_power-pinpoint=${lastHour},${now}.png?&lower_limit=&upper_limit=&size_x=800&size_y=400`}
|
||||
/>
|
||||
<button
|
||||
on:click={() => {
|
||||
fetch(Constants.ErrorReportServer, {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
|
||||
import { onMount } from "svelte"
|
||||
|
||||
export let imageInfo
|
||||
|
@ -8,32 +7,25 @@
|
|||
|
||||
let container: HTMLElement
|
||||
|
||||
|
||||
onMount(() => {
|
||||
console.log("Creating viewer...")
|
||||
const features = [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": { "name": "trap" },
|
||||
"geometry": {
|
||||
"coordinates": [
|
||||
3.742395038713312,
|
||||
51.05237592785801
|
||||
],
|
||||
"type": "Point"
|
||||
}
|
||||
}
|
||||
type: "Feature",
|
||||
properties: { name: "trap" },
|
||||
geometry: {
|
||||
coordinates: [3.742395038713312, 51.05237592785801],
|
||||
type: "Point",
|
||||
},
|
||||
},
|
||||
]
|
||||
const viewer = new PhotoSphereViewerWrapper(container, imageInfo, features)
|
||||
|
||||
|
||||
// console.log(panorama, container)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="./node_modules/pannellum/build/pannellum.css">
|
||||
<link rel="stylesheet" href="./node_modules/pannellum/build/pannellum.css" />
|
||||
</head>
|
||||
<div bind:this={container} class="h-screen w-screen border" style="height: 500px"></div>
|
||||
|
||||
<div bind:this={container} class="h-screen w-screen border" style="height: 500px" />
|
||||
|
|
|
@ -165,7 +165,6 @@ export class Translation extends BaseUIElement {
|
|||
* Which language will be effectively used for the given language of choice?
|
||||
*/
|
||||
public actualLanguage(language: string): "*" | string | undefined {
|
||||
|
||||
const txt = this.translations[language]
|
||||
if (txt !== undefined) {
|
||||
return language
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"contributors": [
|
||||
{
|
||||
"commits": 9369,
|
||||
"commits": 9515,
|
||||
"contributor": "Pieter Vander Vennet"
|
||||
},
|
||||
{
|
||||
"commits": 520,
|
||||
"commits": 546,
|
||||
"contributor": "Robin van der Linde"
|
||||
},
|
||||
{
|
||||
|
@ -49,7 +49,7 @@
|
|||
"contributor": "riQQ"
|
||||
},
|
||||
{
|
||||
"commits": 26,
|
||||
"commits": 27,
|
||||
"contributor": "Osmwithspace"
|
||||
},
|
||||
{
|
||||
|
@ -100,6 +100,10 @@
|
|||
"commits": 15,
|
||||
"contributor": "ToastHawaii"
|
||||
},
|
||||
{
|
||||
"commits": 14,
|
||||
"contributor": "Bastian Greshake Tzovaras"
|
||||
},
|
||||
{
|
||||
"commits": 14,
|
||||
"contributor": "danieldegroot2"
|
||||
|
@ -148,10 +152,6 @@
|
|||
"commits": 8,
|
||||
"contributor": "Mateusz Konieczny"
|
||||
},
|
||||
{
|
||||
"commits": 7,
|
||||
"contributor": "Bastian Greshake Tzovaras"
|
||||
},
|
||||
{
|
||||
"commits": 7,
|
||||
"contributor": "OliNau"
|
||||
|
|
|
@ -181,9 +181,9 @@
|
|||
"ar"
|
||||
],
|
||||
"ER": [
|
||||
"ti",
|
||||
"en",
|
||||
"ar"
|
||||
"ar",
|
||||
"ti"
|
||||
],
|
||||
"ES": [
|
||||
"es",
|
||||
|
@ -445,7 +445,7 @@
|
|||
"en"
|
||||
],
|
||||
"NE": [
|
||||
"fr"
|
||||
"ha"
|
||||
],
|
||||
"NG": [
|
||||
"en"
|
||||
|
|
|
@ -3931,7 +3931,6 @@
|
|||
"MC",
|
||||
"MG",
|
||||
"MU",
|
||||
"NE",
|
||||
"RW",
|
||||
"SC",
|
||||
"SN",
|
||||
|
@ -4700,6 +4699,9 @@
|
|||
"uk": "хауса",
|
||||
"zh_Hant": "豪薩語",
|
||||
"_meta": {
|
||||
"countries": [
|
||||
"NE"
|
||||
],
|
||||
"dir": [
|
||||
"right-to-left",
|
||||
"left-to-right"
|
||||
|
|
|
@ -1981,6 +1981,11 @@
|
|||
"then": "scissors",
|
||||
"icon": "scissors"
|
||||
},
|
||||
{
|
||||
"if": "value=snowflake",
|
||||
"then": "snowflake",
|
||||
"icon": "snowflake"
|
||||
},
|
||||
{
|
||||
"if": "value=square",
|
||||
"then": "square",
|
||||
|
@ -10724,6 +10729,10 @@
|
|||
"if": "value=aerialway",
|
||||
"then": "aerialway - Various forms of transport for passengers and goods that use wires, including cable cars, gondolas, chair lifts, drag lifts, and zip lines."
|
||||
},
|
||||
{
|
||||
"if": "value=all_streets",
|
||||
"then": "all_streets - Layer with (almost) all streets"
|
||||
},
|
||||
{
|
||||
"if": "value=ambulancestation",
|
||||
"then": "ambulancestation - An ambulance station is an area for storage of ambulance vehicles, medical equipment, personal protective equipment, and other medical supplies."
|
||||
|
@ -11006,7 +11015,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=geocoded_image",
|
||||
"then": "geocoded_image - undefined"
|
||||
"then": "geocoded_image - Layer showing green dots where a geocoded image was found. See NearbyImages.svelte. Propreties: 'rotation':number,'spherical':'yes'|'no'"
|
||||
},
|
||||
{
|
||||
"if": "value=ghost_bike",
|
||||
|
@ -11114,7 +11123,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=maproulette_challenge",
|
||||
"then": "maproulette_challenge - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Integrating_Maproulette.md) on how to do this."
|
||||
"then": "maproulette_challenge - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/Docs/Integrating_Maproulette.md) on how to do this."
|
||||
},
|
||||
{
|
||||
"if": "value=maxspeed",
|
||||
|
@ -11400,6 +11409,10 @@
|
|||
"if": "value=usersettings",
|
||||
"then": "usersettings - A special layer which is not meant to be shown on a map, but which is used to set user settings"
|
||||
},
|
||||
{
|
||||
"if": "value=utility_pole",
|
||||
"then": "utility_pole - Layer showing various types of utility poles."
|
||||
},
|
||||
{
|
||||
"if": "value=vending_machine",
|
||||
"then": "vending_machine - Layer showing vending machines"
|
||||
|
@ -11434,7 +11447,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=wayside_shrine",
|
||||
"then": "wayside_shrine - Shrines are religious places that are dedicated to specific deities, saints and other figures of religious importance. Typically, the contain religious depictions and people frequently leave offerings at those places. Wayside shrines are small shrines that can be found next to a road or pathway and are frequented by travellers passing by."
|
||||
"then": "wayside_shrine - Shrines are religious places that are dedicated to specific deities, saints and other figures of religious importance. Typically, the contain religious depictions and people frequently leave offerings at those places. Wayside shrines are small shrines that can be found next to a road or pathway and are frequented by travellers passing by. Wayside crosses can be seen as a sub-type of a wayside shrine, typically in the form of a Christian cross at the side of the road, typically without votive offerings."
|
||||
},
|
||||
{
|
||||
"if": "value=windturbine",
|
||||
|
@ -12464,7 +12477,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -13801,7 +13814,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -15186,7 +15199,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -16575,7 +16588,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -17966,7 +17979,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -19355,7 +19368,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
|
|
@ -337,6 +337,11 @@
|
|||
"then": "scissors",
|
||||
"icon": "scissors"
|
||||
},
|
||||
{
|
||||
"if": "value=snowflake",
|
||||
"then": "snowflake",
|
||||
"icon": "snowflake"
|
||||
},
|
||||
{
|
||||
"if": "value=square",
|
||||
"then": "square",
|
||||
|
@ -603,6 +608,10 @@
|
|||
"if": "value=aerialway",
|
||||
"then": "<b>aerialway</b> (builtin) - Various forms of transport for passengers and goods that use wires, including cable cars, gondolas, chair lifts, drag lifts, and zip lines."
|
||||
},
|
||||
{
|
||||
"if": "value=all_streets",
|
||||
"then": "<b>all_streets</b> (builtin) - Layer with (almost) all streets"
|
||||
},
|
||||
{
|
||||
"if": "value=ambulancestation",
|
||||
"then": "<b>ambulancestation</b> (builtin) - An ambulance station is an area for storage of ambulance vehicles, medical equipment, personal protective equipment, and other medical supplies."
|
||||
|
@ -885,7 +894,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=geocoded_image",
|
||||
"then": "<b>geocoded_image</b> (builtin) - undefined"
|
||||
"then": "<b>geocoded_image</b> (builtin) - Layer showing green dots where a geocoded image was found. See NearbyImages.svelte. Propreties: 'rotation':number,'spherical':'yes'|'no'"
|
||||
},
|
||||
{
|
||||
"if": "value=ghost_bike",
|
||||
|
@ -993,7 +1002,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=maproulette_challenge",
|
||||
"then": "<b>maproulette_challenge</b> (builtin) - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Integrating_Maproulette.md) on how to do this."
|
||||
"then": "<b>maproulette_challenge</b> (builtin) - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/Docs/Integrating_Maproulette.md) on how to do this."
|
||||
},
|
||||
{
|
||||
"if": "value=maxspeed",
|
||||
|
@ -1279,6 +1288,10 @@
|
|||
"if": "value=usersettings",
|
||||
"then": "<b>usersettings</b> (builtin) - A special layer which is not meant to be shown on a map, but which is used to set user settings"
|
||||
},
|
||||
{
|
||||
"if": "value=utility_pole",
|
||||
"then": "<b>utility_pole</b> (builtin) - Layer showing various types of utility poles."
|
||||
},
|
||||
{
|
||||
"if": "value=vending_machine",
|
||||
"then": "<b>vending_machine</b> (builtin) - Layer showing vending machines"
|
||||
|
@ -1313,7 +1326,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=wayside_shrine",
|
||||
"then": "<b>wayside_shrine</b> (builtin) - Shrines are religious places that are dedicated to specific deities, saints and other figures of religious importance. Typically, the contain religious depictions and people frequently leave offerings at those places. Wayside shrines are small shrines that can be found next to a road or pathway and are frequented by travellers passing by."
|
||||
"then": "<b>wayside_shrine</b> (builtin) - Shrines are religious places that are dedicated to specific deities, saints and other figures of religious importance. Typically, the contain religious depictions and people frequently leave offerings at those places. Wayside shrines are small shrines that can be found next to a road or pathway and are frequented by travellers passing by. Wayside crosses can be seen as a sub-type of a wayside shrine, typically in the form of a Christian cross at the side of the road, typically without votive offerings."
|
||||
},
|
||||
{
|
||||
"if": "value=windturbine",
|
||||
|
@ -4394,6 +4407,11 @@
|
|||
"then": "scissors",
|
||||
"icon": "scissors"
|
||||
},
|
||||
{
|
||||
"if": "value=snowflake",
|
||||
"then": "snowflake",
|
||||
"icon": "snowflake"
|
||||
},
|
||||
{
|
||||
"if": "value=square",
|
||||
"then": "square",
|
||||
|
@ -13355,6 +13373,10 @@
|
|||
"if": "value=aerialway",
|
||||
"then": "aerialway - Various forms of transport for passengers and goods that use wires, including cable cars, gondolas, chair lifts, drag lifts, and zip lines."
|
||||
},
|
||||
{
|
||||
"if": "value=all_streets",
|
||||
"then": "all_streets - Layer with (almost) all streets"
|
||||
},
|
||||
{
|
||||
"if": "value=ambulancestation",
|
||||
"then": "ambulancestation - An ambulance station is an area for storage of ambulance vehicles, medical equipment, personal protective equipment, and other medical supplies."
|
||||
|
@ -13637,7 +13659,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=geocoded_image",
|
||||
"then": "geocoded_image - undefined"
|
||||
"then": "geocoded_image - Layer showing green dots where a geocoded image was found. See NearbyImages.svelte. Propreties: 'rotation':number,'spherical':'yes'|'no'"
|
||||
},
|
||||
{
|
||||
"if": "value=ghost_bike",
|
||||
|
@ -13745,7 +13767,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=maproulette_challenge",
|
||||
"then": "maproulette_challenge - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Integrating_Maproulette.md) on how to do this."
|
||||
"then": "maproulette_challenge - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/Docs/Integrating_Maproulette.md) on how to do this."
|
||||
},
|
||||
{
|
||||
"if": "value=maxspeed",
|
||||
|
@ -14031,6 +14053,10 @@
|
|||
"if": "value=usersettings",
|
||||
"then": "usersettings - A special layer which is not meant to be shown on a map, but which is used to set user settings"
|
||||
},
|
||||
{
|
||||
"if": "value=utility_pole",
|
||||
"then": "utility_pole - Layer showing various types of utility poles."
|
||||
},
|
||||
{
|
||||
"if": "value=vending_machine",
|
||||
"then": "vending_machine - Layer showing vending machines"
|
||||
|
@ -14065,7 +14091,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=wayside_shrine",
|
||||
"then": "wayside_shrine - Shrines are religious places that are dedicated to specific deities, saints and other figures of religious importance. Typically, the contain religious depictions and people frequently leave offerings at those places. Wayside shrines are small shrines that can be found next to a road or pathway and are frequented by travellers passing by."
|
||||
"then": "wayside_shrine - Shrines are religious places that are dedicated to specific deities, saints and other figures of religious importance. Typically, the contain religious depictions and people frequently leave offerings at those places. Wayside shrines are small shrines that can be found next to a road or pathway and are frequented by travellers passing by. Wayside crosses can be seen as a sub-type of a wayside shrine, typically in the form of a Christian cross at the side of the road, typically without votive offerings."
|
||||
},
|
||||
{
|
||||
"if": "value=windturbine",
|
||||
|
@ -15115,7 +15141,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -16489,7 +16515,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -17912,7 +17938,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -19337,7 +19363,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -20763,7 +20789,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -22188,7 +22214,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -25907,6 +25933,11 @@
|
|||
"then": "scissors",
|
||||
"icon": "scissors"
|
||||
},
|
||||
{
|
||||
"if": "value=snowflake",
|
||||
"then": "snowflake",
|
||||
"icon": "snowflake"
|
||||
},
|
||||
{
|
||||
"if": "value=square",
|
||||
"then": "square",
|
||||
|
@ -35143,6 +35174,10 @@
|
|||
"if": "value=aerialway",
|
||||
"then": "aerialway - Various forms of transport for passengers and goods that use wires, including cable cars, gondolas, chair lifts, drag lifts, and zip lines."
|
||||
},
|
||||
{
|
||||
"if": "value=all_streets",
|
||||
"then": "all_streets - Layer with (almost) all streets"
|
||||
},
|
||||
{
|
||||
"if": "value=ambulancestation",
|
||||
"then": "ambulancestation - An ambulance station is an area for storage of ambulance vehicles, medical equipment, personal protective equipment, and other medical supplies."
|
||||
|
@ -35425,7 +35460,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=geocoded_image",
|
||||
"then": "geocoded_image - undefined"
|
||||
"then": "geocoded_image - Layer showing green dots where a geocoded image was found. See NearbyImages.svelte. Propreties: 'rotation':number,'spherical':'yes'|'no'"
|
||||
},
|
||||
{
|
||||
"if": "value=ghost_bike",
|
||||
|
@ -35533,7 +35568,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=maproulette_challenge",
|
||||
"then": "maproulette_challenge - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Integrating_Maproulette.md) on how to do this."
|
||||
"then": "maproulette_challenge - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/Docs/Integrating_Maproulette.md) on how to do this."
|
||||
},
|
||||
{
|
||||
"if": "value=maxspeed",
|
||||
|
@ -35819,6 +35854,10 @@
|
|||
"if": "value=usersettings",
|
||||
"then": "usersettings - A special layer which is not meant to be shown on a map, but which is used to set user settings"
|
||||
},
|
||||
{
|
||||
"if": "value=utility_pole",
|
||||
"then": "utility_pole - Layer showing various types of utility poles."
|
||||
},
|
||||
{
|
||||
"if": "value=vending_machine",
|
||||
"then": "vending_machine - Layer showing vending machines"
|
||||
|
@ -35853,7 +35892,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=wayside_shrine",
|
||||
"then": "wayside_shrine - Shrines are religious places that are dedicated to specific deities, saints and other figures of religious importance. Typically, the contain religious depictions and people frequently leave offerings at those places. Wayside shrines are small shrines that can be found next to a road or pathway and are frequented by travellers passing by."
|
||||
"then": "wayside_shrine - Shrines are religious places that are dedicated to specific deities, saints and other figures of religious importance. Typically, the contain religious depictions and people frequently leave offerings at those places. Wayside shrines are small shrines that can be found next to a road or pathway and are frequented by travellers passing by. Wayside crosses can be seen as a sub-type of a wayside shrine, typically in the form of a Christian cross at the side of the road, typically without votive offerings."
|
||||
},
|
||||
{
|
||||
"if": "value=windturbine",
|
||||
|
@ -36930,7 +36969,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -38355,7 +38394,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -39831,7 +39870,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -41307,7 +41346,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -42784,7 +42823,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
@ -44260,7 +44299,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
|
|
@ -667,7 +667,7 @@
|
|||
},
|
||||
{
|
||||
"if": "value=nsi",
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. "
|
||||
"then": "<b>nsi</b> Gives a list of possible suggestions for a brand or operator tag. Note: this is detected automatically; there is no need to explicitly set this"
|
||||
},
|
||||
{
|
||||
"if": "value=currency",
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
"contributor": "Allan Nordhøy"
|
||||
},
|
||||
{
|
||||
"commits": 89,
|
||||
"commits": 90,
|
||||
"contributor": "Robin van der Linde"
|
||||
},
|
||||
{
|
||||
|
@ -236,6 +236,10 @@
|
|||
"commits": 8,
|
||||
"contributor": "Vinicius"
|
||||
},
|
||||
{
|
||||
"commits": 7,
|
||||
"contributor": "Lukáš Jelínek"
|
||||
},
|
||||
{
|
||||
"commits": 7,
|
||||
"contributor": "Franco"
|
||||
|
@ -252,10 +256,6 @@
|
|||
"commits": 7,
|
||||
"contributor": "Niels Elgaard Larsen"
|
||||
},
|
||||
{
|
||||
"commits": 6,
|
||||
"contributor": "Lukáš Jelínek"
|
||||
},
|
||||
{
|
||||
"commits": 6,
|
||||
"contributor": "Juele juele"
|
||||
|
|
|
@ -12,6 +12,6 @@ panoramax.imageInfo(imgId).then((imageInfo: ImageData) => {
|
|||
})*/
|
||||
|
||||
let pkey = 1199645818028177
|
||||
new Mapillary().DownloadImageInfo(pkey).then(imageInfo => {
|
||||
new Mapillary().DownloadImageInfo(pkey).then((imageInfo) => {
|
||||
new Test({ target, props: { imageInfo } })
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue