From 4f2bbf4b54f700353ccc4e2928549c853d656202 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 11 Mar 2023 02:37:07 +0100 Subject: [PATCH 001/257] refactoring(maplibre): add RasterLayer info, add background switch, add default global layers --- Logic/Actors/AvailableBaseLayers.ts | 51 --- .../AvailableBaseLayersImplementation.ts | 309 ------------- Logic/Actors/BackgroundLayerResetter.ts | 55 +-- Models/BaseLayer.ts | 10 - Models/RasterLayers.ts | 276 ++++++++++++ UI/Base/MinimapImplementation.ts | 2 - UI/BigComponents/BackgroundSelector.ts | 41 -- UI/Map/MapLibreAdaptor.ts | 172 +++++++ UI/Map/MaplibreMap.svelte | 43 ++ UI/Map/RasterLayerPicker.svelte | 18 + assets/global-raster-layers.json | 97 ++++ package-lock.json | 418 +++++++++++++++++- package.json | 2 + test.ts | 81 +++- 14 files changed, 1103 insertions(+), 472 deletions(-) delete mode 100644 Logic/Actors/AvailableBaseLayers.ts delete mode 100644 Logic/Actors/AvailableBaseLayersImplementation.ts delete mode 100644 Models/BaseLayer.ts create mode 100644 Models/RasterLayers.ts delete mode 100644 UI/BigComponents/BackgroundSelector.ts create mode 100644 UI/Map/MapLibreAdaptor.ts create mode 100644 UI/Map/MaplibreMap.svelte create mode 100644 UI/Map/RasterLayerPicker.svelte create mode 100644 assets/global-raster-layers.json diff --git a/Logic/Actors/AvailableBaseLayers.ts b/Logic/Actors/AvailableBaseLayers.ts deleted file mode 100644 index f6de0451af..0000000000 --- a/Logic/Actors/AvailableBaseLayers.ts +++ /dev/null @@ -1,51 +0,0 @@ -import BaseLayer from "../../Models/BaseLayer" -import { ImmutableStore, Store, UIEventSource } from "../UIEventSource" -import Loc from "../../Models/Loc" - -export interface AvailableBaseLayersObj { - readonly osmCarto: BaseLayer - layerOverview: BaseLayer[] - - AvailableLayersAt(location: Store): Store - - SelectBestLayerAccordingTo( - location: Store, - preferedCategory: Store - ): Store -} - -/** - * Calculates which layers are available at the current location - * Changes the basemap - */ -export default class AvailableBaseLayers { - public static layerOverview: BaseLayer[] - public static osmCarto: BaseLayer - - private static implementation: AvailableBaseLayersObj - - static AvailableLayersAt(location: Store): Store { - return ( - AvailableBaseLayers.implementation?.AvailableLayersAt(location) ?? - new ImmutableStore([]) - ) - } - - static SelectBestLayerAccordingTo( - location: Store, - preferedCategory: UIEventSource - ): Store { - return ( - AvailableBaseLayers.implementation?.SelectBestLayerAccordingTo( - location, - preferedCategory - ) ?? new ImmutableStore(undefined) - ) - } - - public static implement(backend: AvailableBaseLayersObj) { - AvailableBaseLayers.layerOverview = backend.layerOverview - AvailableBaseLayers.osmCarto = backend.osmCarto - AvailableBaseLayers.implementation = backend - } -} diff --git a/Logic/Actors/AvailableBaseLayersImplementation.ts b/Logic/Actors/AvailableBaseLayersImplementation.ts deleted file mode 100644 index 8551db5245..0000000000 --- a/Logic/Actors/AvailableBaseLayersImplementation.ts +++ /dev/null @@ -1,309 +0,0 @@ -import BaseLayer from "../../Models/BaseLayer" -import { Store, Stores } from "../UIEventSource" -import Loc from "../../Models/Loc" -import { GeoOperations } from "../GeoOperations" -import * as editorlayerindex from "../../assets/editor-layer-index.json" -import * as L from "leaflet" -import { TileLayer } from "leaflet" -import * as X from "leaflet-providers" -import { Utils } from "../../Utils" -import { AvailableBaseLayersObj } from "./AvailableBaseLayers" -import { BBox } from "../BBox" - -export default class AvailableBaseLayersImplementation implements AvailableBaseLayersObj { - public readonly osmCarto: BaseLayer = { - id: "osm", - name: "OpenStreetMap", - layer: () => - AvailableBaseLayersImplementation.CreateBackgroundLayer( - "osm", - "OpenStreetMap", - "https://tile.openstreetmap.org/{z}/{x}/{y}.png", - "OpenStreetMap", - "https://openStreetMap.org/copyright", - 19, - false, - false - ), - feature: null, - max_zoom: 19, - min_zoom: 0, - isBest: true, // Of course, OpenStreetMap is the best map! - category: "osmbasedmap", - } - - public readonly layerOverview = AvailableBaseLayersImplementation.LoadRasterIndex().concat( - AvailableBaseLayersImplementation.LoadProviderIndex() - ) - public readonly globalLayers = this.layerOverview.filter( - (layer) => layer.feature?.geometry === undefined || layer.feature?.geometry === null - ) - public readonly localLayers = this.layerOverview.filter( - (layer) => layer.feature?.geometry !== undefined && layer.feature?.geometry !== null - ) - - private static LoadRasterIndex(): BaseLayer[] { - const layers: BaseLayer[] = [] - // @ts-ignore - const features = editorlayerindex.features - for (const i in features) { - const layer = features[i] - const props = layer.properties - - if (props.type === "bing") { - // A lot of work to implement - see https://github.com/pietervdvn/MapComplete/issues/648 - continue - } - - if (props.id === "MAPNIK") { - // Already added by default - continue - } - - if (props.overlay) { - continue - } - - if (props.url.toLowerCase().indexOf("apikey") > 0) { - continue - } - - if (props.max_zoom < 19) { - // We want users to zoom to level 19 when adding a point - // If they are on a layer which hasn't enough precision, they can not zoom far enough. This is confusing, so we don't use this layer - continue - } - - if (props.name === undefined) { - console.warn("Editor layer index: name not defined on ", props) - continue - } - - const leafletLayer: () => TileLayer = () => - AvailableBaseLayersImplementation.CreateBackgroundLayer( - props.id, - props.name, - props.url, - props.name, - props.license_url, - props.max_zoom, - props.type.toLowerCase() === "wms", - props.type.toLowerCase() === "wmts" - ) - - // Note: if layer.geometry is null, there is global coverage for this layer - layers.push({ - id: props.id, - max_zoom: props.max_zoom ?? 19, - min_zoom: props.min_zoom ?? 1, - name: props.name, - layer: leafletLayer, - feature: layer.geometry !== null ? layer : null, - isBest: props.best ?? false, - category: props.category, - }) - } - return layers - } - - private static LoadProviderIndex(): BaseLayer[] { - // @ts-ignore - X // Import X to make sure the namespace is not optimized away - function l(id: string, name: string): BaseLayer { - try { - const layer: any = L.tileLayer.provider(id, undefined) - return { - feature: null, - id: id, - name: name, - layer: () => - L.tileLayer.provider(id, { - maxNativeZoom: layer.options?.maxZoom, - maxZoom: Math.max(layer.options?.maxZoom ?? 19, 21), - }), - min_zoom: 1, - max_zoom: layer.options.maxZoom, - category: "osmbasedmap", - isBest: false, - } - } catch (e) { - console.error("Could not find provided layer", name, e) - return null - } - } - - const layers = [ - l("Stamen.TonerLite", "Toner Lite (by Stamen)"), - l("Stamen.TonerBackground", "Toner Background - no labels (by Stamen)"), - l("Stamen.Watercolor", "Watercolor (by Stamen)"), - l("CartoDB.Positron", "Positron (by CartoDB)"), - l("CartoDB.PositronNoLabels", "Positron - no labels (by CartoDB)"), - l("CartoDB.Voyager", "Voyager (by CartoDB)"), - l("CartoDB.VoyagerNoLabels", "Voyager - no labels (by CartoDB)"), - l("CartoDB.DarkMatter", "Dark Matter (by CartoDB)"), - l("CartoDB.DarkMatterNoLabels", "Dark Matter - no labels (by CartoDB)"), - ] - return Utils.NoNull(layers) - } - - /** - * Converts a layer from the editor-layer-index into a tilelayer usable by leaflet - */ - private static CreateBackgroundLayer( - id: string, - name: string, - url: string, - attribution: string, - attributionUrl: string, - maxZoom: number, - isWms: boolean, - isWMTS?: boolean - ): TileLayer { - url = url.replace("{zoom}", "{z}").replace("&BBOX={bbox}", "").replace("&bbox={bbox}", "") - - const subdomainsMatch = url.match(/{switch:[^}]*}/) - let domains: string[] = [] - if (subdomainsMatch !== null) { - let domainsStr = subdomainsMatch[0].substr("{switch:".length) - domainsStr = domainsStr.substr(0, domainsStr.length - 1) - domains = domainsStr.split(",") - url = url.replace(/{switch:[^}]*}/, "{s}") - } - - if (isWms) { - url = url.replace("&SRS={proj}", "") - url = url.replace("&srs={proj}", "") - const paramaters = [ - "format", - "layers", - "version", - "service", - "request", - "styles", - "transparent", - "version", - ] - const urlObj = new URL(url) - - const isUpper = urlObj.searchParams["LAYERS"] !== null - const options = { - maxZoom: Math.max(maxZoom ?? 19, 21), - maxNativeZoom: maxZoom ?? 19, - attribution: attribution + " | ", - subdomains: domains, - uppercase: isUpper, - transparent: false, - } - - for (const paramater of paramaters) { - let p = paramater - if (isUpper) { - p = paramater.toUpperCase() - } - options[paramater] = urlObj.searchParams.get(p) - } - - if (options.transparent === null) { - options.transparent = false - } - - return L.tileLayer.wms(urlObj.protocol + "//" + urlObj.host + urlObj.pathname, options) - } - - if (attributionUrl) { - attribution = `${attribution}` - } - - return L.tileLayer(url, { - attribution: attribution, - maxZoom: Math.max(21, maxZoom ?? 19), - maxNativeZoom: maxZoom ?? 19, - minZoom: 1, - // @ts-ignore - wmts: isWMTS ?? false, - subdomains: domains, - }) - } - - public AvailableLayersAt(location: Store): Store { - return Stores.ListStabilized( - location.map((currentLocation) => { - if (currentLocation === undefined) { - return this.layerOverview - } - return this.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat) - }) - ) - } - - public SelectBestLayerAccordingTo( - location: Store, - preferedCategory: Store - ): Store { - return this.AvailableLayersAt(location).map( - (available) => { - // First float all 'best layers' to the top - available.sort((a, b) => { - if (a.isBest && b.isBest) { - return 0 - } - if (!a.isBest) { - return 1 - } - - return -1 - }) - - if (preferedCategory.data === undefined) { - return available[0] - } - - let prefered: string[] - if (typeof preferedCategory.data === "string") { - prefered = [preferedCategory.data] - } else { - prefered = preferedCategory.data - } - - prefered.reverse(/*New list, inplace reverse is fine*/) - for (const category of prefered) { - //Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top - available.sort((a, b) => { - if (a.category === category && b.category === category) { - return 0 - } - if (a.category !== category) { - return 1 - } - - return -1 - }) - } - return available[0] - }, - [preferedCategory] - ) - } - - private CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] { - const availableLayers = [this.osmCarto] - if (lon === undefined || lat === undefined) { - return availableLayers.concat(this.globalLayers) - } - const lonlat: [number, number] = [lon, lat] - for (const layerOverviewItem of this.localLayers) { - const layer = layerOverviewItem - const bbox = BBox.get(layer.feature) - - if (!bbox.contains(lonlat)) { - continue - } - - if (GeoOperations.inside(lonlat, layer.feature)) { - availableLayers.push(layer) - } - } - - return availableLayers.concat(this.globalLayers) - } -} diff --git a/Logic/Actors/BackgroundLayerResetter.ts b/Logic/Actors/BackgroundLayerResetter.ts index ee559b4fb2..f2bf154d3b 100644 --- a/Logic/Actors/BackgroundLayerResetter.ts +++ b/Logic/Actors/BackgroundLayerResetter.ts @@ -1,49 +1,42 @@ -import { UIEventSource } from "../UIEventSource" -import BaseLayer from "../../Models/BaseLayer" -import AvailableBaseLayers from "./AvailableBaseLayers" -import Loc from "../../Models/Loc" +import { Store, UIEventSource } from "../UIEventSource" import { Utils } from "../../Utils" +import { + AvailableRasterLayers, + RasterLayerPolygon, + RasterLayerUtils, +} from "../../Models/RasterLayers" /** - * Sets the current background layer to a layer that is actually available + * When a user pans around on the map, they might pan out of the range of the current background raster layer. + * This actor will then quickly select a (best) raster layer of the same category which is available */ export default class BackgroundLayerResetter { constructor( - currentBackgroundLayer: UIEventSource, - location: UIEventSource, - availableLayers: UIEventSource, - defaultLayerId: string = undefined + currentBackgroundLayer: UIEventSource, + availableLayers: Store ) { if (Utils.runningFromConsole) { return } - defaultLayerId = defaultLayerId ?? AvailableBaseLayers.osmCarto.id + // Change the baseLayer back to OSM if we go out of the current range of the layer + availableLayers.addCallbackAndRunD((availableLayers) => { + // We only check on move/on change of the availableLayers + const currentBgPolygon: RasterLayerPolygon | undefined = currentBackgroundLayer.data - // Change the baselayer back to OSM if we go out of the current range of the layer - availableLayers.addCallbackAndRun((availableLayers) => { - let defaultLayer = undefined - const currentLayer = currentBackgroundLayer.data.id - for (const availableLayer of availableLayers) { - if (availableLayer.id === currentLayer) { - if (availableLayer.max_zoom < location.data.zoom) { - break - } - - if (availableLayer.min_zoom > location.data.zoom) { - break - } - if (availableLayer.id === defaultLayerId) { - defaultLayer = availableLayer - } - return // All good - the current layer still works! - } + if (availableLayers.findIndex((available) => currentBgPolygon == available) >= 0) { + // Still available! + return } + // Oops, we panned out of range for this layer! - console.log( - "AvailableBaseLayers-actor: detected that the current bounds aren't sufficient anymore - reverting to OSM standard" + // What is the 'best' map of the same category which is available? + const availableInSameCat = RasterLayerUtils.SelectBestLayerAccordingTo( + availableLayers, + currentBgPolygon?.properties?.category ?? "osmbasedmap" ) - currentBackgroundLayer.setData(defaultLayer ?? AvailableBaseLayers.osmCarto) + console.log("Selecting a different layer:", availableInSameCat.properties.id) + currentBackgroundLayer.setData(availableInSameCat ?? AvailableRasterLayers.osmCarto) }) } } diff --git a/Models/BaseLayer.ts b/Models/BaseLayer.ts deleted file mode 100644 index 1e4c2a9335..0000000000 --- a/Models/BaseLayer.ts +++ /dev/null @@ -1,10 +0,0 @@ -export default interface BaseLayer { - id: string - name: string - layer: () => any /*leaflet.TileLayer - not importing as it breaks scripts*/ - max_zoom: number - min_zoom: number - feature: any - isBest?: boolean - category?: "map" | "osmbasedmap" | "photo" | "historicphoto" | string -} diff --git a/Models/RasterLayers.ts b/Models/RasterLayers.ts new file mode 100644 index 0000000000..602af9c3b0 --- /dev/null +++ b/Models/RasterLayers.ts @@ -0,0 +1,276 @@ +import { Feature, Polygon } from "geojson" +import * as editorlayerindex from "../assets/editor-layer-index.json" +import * as globallayers from "../assets/global-raster-layers.json" +import { BBox } from "../Logic/BBox" + +export class AvailableRasterLayers { + public static EditorLayerIndex: (Feature & + RasterLayerPolygon)[] = editorlayerindex.features + public static globalLayers: RasterLayerPolygon[] = globallayers.layers.map( + (properties) => + { + type: "Feature", + properties, + geometry: BBox.global.asGeometry(), + } + ) + public static readonly osmCartoProperties: RasterLayerProperties = { + id: "osm", + name: "OpenStreetMap", + url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + attribution: { + text: "OpenStreetMap", + url: "https://openStreetMap.org/copyright", + }, + best: true, + max_zoom: 19, + min_zoom: 0, + category: "osmbasedmap", + } + + public static readonly osmCarto: RasterLayerPolygon = { + type: "Feature", + properties: AvailableRasterLayers.osmCartoProperties, + geometry: BBox.global.asGeometry(), + } +} + +export class RasterLayerUtils { + public static SelectBestLayerAccordingTo( + available: RasterLayerPolygon[], + preferredCategory: string | string[] + ): RasterLayerPolygon { + available = [...available] + + if (preferredCategory === undefined) { + return available[0] + } + + let prefered: string[] + if (typeof preferredCategory === "string") { + prefered = [preferredCategory] + } else { + prefered = preferredCategory + } + + for (let i = prefered.length - 1; i >= 0; i--) { + const category = prefered[i] + //Then sort all layers of the preferred type to the top. Stability of the sorting will force a 'best' photo layer on top + available.sort((ap, bp) => { + const a = ap.properties + const b = bp.properties + if (a.category === category && b.category === category) { + return 0 + } + if (a.category !== category) { + return 1 + } + + return -1 + }) + } + const best = available.find((l) => l.properties.best) + if (best) { + return best + } + return available[0] + } +} + +export type RasterLayerPolygon = Feature + +export interface RasterLayerProperties { + /** + * The name of the imagery source + */ + readonly name: string + + readonly id: string + + readonly url: string + readonly category?: + | string + | "photo" + | "map" + | "historicmap" + | "osmbasedmap" + | "historicphoto" + | "qa" + | "elevation" + | "other" + + readonly attribution?: { + readonly url?: string + readonly text?: string + readonly html?: string + readonly required?: boolean + } + + readonly min_zoom?: number + readonly max_zoom?: number + + readonly best?: boolean +} + +/** + * Information about a raster tile layer + * + * Based on the spec here https://github.com/osmlab/editor-layer-index/blob/gh-pages/schema.json + * which was then converted with http://borischerny.com/json-schema-to-typescript-browser/ + */ +export interface EditorLayerIndexProperties extends RasterLayerProperties { + /** + * The name of the imagery source + */ + readonly name: string + /** + * Whether the imagery name should be translated + */ + readonly i18n?: boolean + readonly type: "tms" | "wms" | "bing" | "scanex" | "wms_endpoint" | "wmts" + /** + * A rough categorisation of different types of layers. See https://github.com/osmlab/editor-layer-index/blob/gh-pages/CONTRIBUTING.md#categories for a description of the individual categories. + */ + readonly category?: + | "photo" + | "map" + | "historicmap" + | "osmbasedmap" + | "historicphoto" + | "qa" + | "elevation" + | "other" + /** + * A URL template for imagery tiles + */ + readonly url: string + readonly min_zoom?: number + readonly max_zoom?: number + /** + * explicit/implicit permission by the owner for use in OSM + */ + readonly permission_osm?: "explicit" | "implicit" | "no" + /** + * A URL for the license or permissions for the imagery + */ + readonly license_url?: string + /** + * A URL for the privacy policy of the operator or false if there is no existing privacy policy for tis imagery. + */ + readonly privacy_policy_url?: string | boolean + /** + * A unique identifier for the source; used in imagery_used changeset tag + */ + readonly id: string + /** + * A short English-language description of the source + */ + readonly description?: string + /** + * The ISO 3166-1 alpha-2 two letter country code in upper case. Use ZZ for unknown or multiple. + */ + readonly country_code?: string + /** + * Whether this imagery should be shown in the default world-wide menu + */ + readonly default?: boolean + /** + * Whether this imagery is the best source for the region + */ + readonly best?: boolean + /** + * The age of the oldest imagery or data in the source, as an RFC3339 date or leading portion of one + */ + readonly start_date?: string + /** + * The age of the newest imagery or data in the source, as an RFC3339 date or leading portion of one + */ + readonly end_date?: string + /** + * HTTP header to check for information if the tile is invalid + */ + readonly no_tile_header?: { + /** + * This interface was referenced by `undefined`'s JSON-Schema definition + * via the `patternProperty` "^.*$". + */ + [k: string]: string[] | null + } + /** + * 'true' if tiles are transparent and can be overlaid on another source + */ + readonly overlay?: boolean & string + readonly available_projections?: string[] + readonly attribution?: { + readonly url?: string + readonly text?: string + readonly html?: string + readonly required?: boolean + } + /** + * A URL for an image, that can be displayed in the list of imagery layers next to the name + */ + readonly icon?: string + /** + * A link to an EULA text that has to be accepted by the user, before the imagery source is added. Can contain {lang} to be replaced by a current user language wiki code (like FR:) or an empty string for the default English text. + */ + readonly eula?: string + /** + * A URL for an image, that is displayed in the mapview for attribution + */ + readonly "logo-image"?: string + /** + * Customized text for the terms of use link (default is "Background Terms of Use") + */ + readonly "terms-of-use-text"?: string + /** + * Specify a checksum for tiles, which aren't real tiles. `type` is the digest type and can be MD5, SHA-1, SHA-256, SHA-384 and SHA-512, value is the hex encoded checksum in lower case. To create a checksum save the tile as file and upload it to e.g. https://defuse.ca/checksums.htm. + */ + readonly "no-tile-checksum"?: string + /** + * header-name attribute specifies a header returned by tile server, that will be shown as `metadata-key` attribute in Show Tile Info dialog + */ + readonly "metadata-header"?: string + /** + * Set to `true` if imagery source is properly aligned and does not need imagery offset adjustments. This is used for OSM based sources too. + */ + readonly "valid-georeference"?: boolean + /** + * Size of individual tiles delivered by a TMS service + */ + readonly "tile-size"?: number + /** + * Whether tiles status can be accessed by appending /status to the tile URL and can be submitted for re-rendering by appending /dirty. + */ + readonly "mod-tile-features"?: string + /** + * HTTP headers to be sent to server. It has two attributes header-name and header-value. May be specified multiple times. + */ + readonly "custom-http-headers"?: { + readonly "header-name"?: string + readonly "header-value"?: string + } + /** + * Default layer to open (when using WMS_ENDPOINT type). Contains list of layer tag with two attributes - name and style, e.g. `"default-layers": ["layer": { name="Basisdata_NP_Basiskart_JanMayen_WMTS_25829" "style":"default" } ]` (not allowed in `mirror` attribute) + */ + readonly "default-layers"?: { + layer?: { + "layer-name"?: string + "layer-style"?: string + [k: string]: unknown + } + [k: string]: unknown + }[] + /** + * format to use when connecting tile server (when using WMS_ENDPOINT type) + */ + readonly format?: string + /** + * If `true` transparent tiles will be requested from WMS server + */ + readonly transparent?: boolean & string + /** + * minimum expiry time for tiles in seconds. The larger the value, the longer entry in cache will be considered valid + */ + readonly "minimum-tile-expire"?: number +} diff --git a/UI/Base/MinimapImplementation.ts b/UI/Base/MinimapImplementation.ts index 271bd72539..a830f6b1d3 100644 --- a/UI/Base/MinimapImplementation.ts +++ b/UI/Base/MinimapImplementation.ts @@ -11,7 +11,6 @@ import { BBox } from "../../Logic/BBox" import "leaflet-polylineoffset" import { SimpleMapScreenshoter } from "leaflet-simple-map-screenshoter" import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch" -import AvailableBaseLayersImplementation from "../../Logic/Actors/AvailableBaseLayersImplementation" import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" import ShowDataLayerImplementation from "../ShowDataLayer/ShowDataLayerImplementation" import FilteredLayer from "../../Models/FilteredLayer" @@ -127,7 +126,6 @@ export default class MinimapImplementation extends BaseUIElement implements Mini } public static initialize() { - AvailableBaseLayers.implement(new AvailableBaseLayersImplementation()) Minimap.createMiniMap = (options) => new MinimapImplementation(options) ShowDataLayer.actualContstructor = (options) => new ShowDataLayerImplementation(options) StrayClickHandler.construct = ( diff --git a/UI/BigComponents/BackgroundSelector.ts b/UI/BigComponents/BackgroundSelector.ts deleted file mode 100644 index 035812f685..0000000000 --- a/UI/BigComponents/BackgroundSelector.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { DropDown } from "../Input/DropDown" -import Translations from "../i18n/Translations" -import State from "../../State" -import BaseLayer from "../../Models/BaseLayer" -import { VariableUiElement } from "../Base/VariableUIElement" -import { Store } from "../../Logic/UIEventSource" - -export default class BackgroundSelector extends VariableUiElement { - constructor(state: { availableBackgroundLayers?: Store }) { - const available = state.availableBackgroundLayers?.map((available) => { - if (available === undefined) { - return undefined - } - const baseLayers: { value: BaseLayer; shown: string }[] = [] - for (const i in available) { - if (!available.hasOwnProperty(i)) { - continue - } - const layer: BaseLayer = available[i] - baseLayers.push({ value: layer, shown: layer.name ?? "id:" + layer.id }) - } - return baseLayers - }) - - super( - available?.map((baseLayers) => { - if (baseLayers === undefined || baseLayers.length <= 1) { - return undefined - } - return new DropDown( - Translations.t.general.backgroundMap.Clone(), - baseLayers, - State.state.backgroundLayer, - { - select_class: "bg-indigo-100 p-1 rounded hover:bg-indigo-200 w-full", - } - ) - }) - ) - } -} diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts new file mode 100644 index 0000000000..f57b076b1a --- /dev/null +++ b/UI/Map/MapLibreAdaptor.ts @@ -0,0 +1,172 @@ +import { Store, UIEventSource } from "../../Logic/UIEventSource" +import type { Map as MLMap } from "maplibre-gl" +import { + EditorLayerIndexProperties, + RasterLayerPolygon, + RasterLayerProperties, +} from "../../Models/RasterLayers" +import { Utils } from "../../Utils" +import Loc from "../../Models/Loc" + +export class MapLibreAdaptor { + private readonly _maplibreMap: Store + private readonly _backgroundLayer?: Store + + private _currentRasterLayer: string = undefined + + constructor( + maplibreMap: Store, + state?: { + // availableBackgroundLayers: Store + /** + * The current background layer + */ + readonly backgroundLayer?: Store + readonly locationControl?: UIEventSource + } + ) { + this._maplibreMap = maplibreMap + this._backgroundLayer = state.backgroundLayer + + const self = this + this._backgroundLayer?.addCallback((_) => self.setBackground()) + + maplibreMap.addCallbackAndRunD((map) => { + map.on("load", () => { + self.setBackground() + }) + if (state.locationControl) { + self.MoveMapToCurrentLoc(state.locationControl.data) + map.on("moveend", () => { + const dt = state.locationControl.data + dt.lon = map.getCenter().lng + dt.lat = map.getCenter().lat + dt.zoom = map.getZoom() + state.locationControl.ping() + }) + } + }) + + state.locationControl.addCallbackAndRunD((loc) => { + self.MoveMapToCurrentLoc(loc) + }) + } + + private MoveMapToCurrentLoc(loc: Loc) { + const map = this._maplibreMap.data + if (map === undefined || loc === undefined) { + return + } + if (map.getZoom() !== loc.zoom) { + map.setZoom(loc.zoom) + } + const center = map.getCenter() + if (center.lng !== loc.lon || center.lat !== loc.lat) { + map.setCenter({ lng: loc.lon, lat: loc.lat }) + } + } + + /** + * Prepares an ELI-URL to be compatible with mapbox + */ + private static prepareWmsURL(url: string, size: number = 256) { + // ELI: LAYERS=OGWRGB13_15VL&STYLES=&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap + // PROD: SERVICE=WMS&REQUEST=GetMap&LAYERS=OGWRGB13_15VL&STYLES=&FORMAT=image/jpeg&TRANSPARENT=false&VERSION=1.3.0&WIDTH=256&HEIGHT=256&CRS=EPSG:3857&BBOX=488585.4847988467,6590094.830634755,489196.9810251281,6590706.32686104 + + const toReplace = { + "{bbox}": "{bbox-epsg-3857}", + "{proj}": "EPSG:3857", + "{width}": "" + size, + "{height}": "" + size, + "{zoom}": "{z}", + } + + for (const key in toReplace) { + url = url.replace(new RegExp(key), toReplace[key]) + } + + const subdomains = url.match(/\{switch:([a-zA-Z0-9,]*)}/) + if (subdomains !== null) { + console.log("Found a switch:", subdomains) + const options = subdomains[1].split(",") + const option = options[Math.floor(Math.random() * options.length)] + url = url.replace(subdomains[0], option) + } + + return url + } + + private async awaitStyleIsLoaded(): Promise { + const map = this._maplibreMap.data + if (map === undefined) { + return + } + while (!map.isStyleLoaded()) { + await Utils.waitFor(250) + } + } + + private removeCurrentLayer(map: MLMap) { + if (this._currentRasterLayer) { + // hide the previous layer + console.log("Removing previous layer", this._currentRasterLayer) + map.removeLayer(this._currentRasterLayer) + map.removeSource(this._currentRasterLayer) + } + } + + private async setBackground() { + const map = this._maplibreMap.data + if (map === undefined) { + return + } + const background: RasterLayerProperties = this._backgroundLayer?.data?.properties + if (background !== undefined && this._currentRasterLayer === background.id) { + // already the correct background layer, nothing to do + return + } + await this.awaitStyleIsLoaded() + + if (background !== this._backgroundLayer?.data?.properties) { + // User selected another background in the meantime... abort + return + } + + if (background !== undefined && this._currentRasterLayer === background.id) { + // already the correct background layer, nothing to do + return + } + if (background === undefined) { + // no background to set + this.removeCurrentLayer(map) + this._currentRasterLayer = undefined + return + } + + map.addSource(background.id, { + type: "raster", + // use the tiles option to specify a 256WMS tile source URL + // https://maplibre.org/maplibre-gl-js-docs/style-spec/sources/ + tiles: [MapLibreAdaptor.prepareWmsURL(background.url, background["tile-size"] ?? 256)], + tileSize: background["tile-size"] ?? 256, + minzoom: background["min_zoom"] ?? 1, + maxzoom: background["max_zoom"] ?? 25, + // scheme: background["type"] === "tms" ? "tms" : "xyz", + }) + + map.addLayer( + { + id: background.id, + type: "raster", + source: background.id, + paint: {}, + }, + background.category === "osmbasedmap" || background.category === "map" + ? undefined + : "aeroway_fill" + ) + await this.awaitStyleIsLoaded() + this.removeCurrentLayer(map) + this._currentRasterLayer = background?.id + } +} diff --git a/UI/Map/MaplibreMap.svelte b/UI/Map/MaplibreMap.svelte new file mode 100644 index 0000000000..25d9462d23 --- /dev/null +++ b/UI/Map/MaplibreMap.svelte @@ -0,0 +1,43 @@ + +
+ +
+ + diff --git a/UI/Map/RasterLayerPicker.svelte b/UI/Map/RasterLayerPicker.svelte new file mode 100644 index 0000000000..129d992e0b --- /dev/null +++ b/UI/Map/RasterLayerPicker.svelte @@ -0,0 +1,18 @@ + + + diff --git a/assets/global-raster-layers.json b/assets/global-raster-layers.json new file mode 100644 index 0000000000..c33f71e080 --- /dev/null +++ b/assets/global-raster-layers.json @@ -0,0 +1,97 @@ +{ + "layers": [ + { + "id": "Stamen.TonerLite", + "name": "Toner Lite (by Stamen)", + "url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.png", + "category": "osmbasedmap", + "attribution": { + "html": "Map tiles by Stamen Design, CC BY 3.0 — Map data {attribution.OpenStreetMap}" + }, + "min_zoom": 0, + "max_zoom": 20 + }, + { + "id": "Stamen.TonerBackground", + "name": "Toner Background - no labels (by Stamen)", + "category": "osmbasedmap", + "url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/toner-background/{z}/{x}/{y}.png", + "attribution": { + "html": "Map tiles by Stamen Design, CC BY 3.0 — Map data {attribution.OpenStreetMap}" + }, + "min_zoom": 0, + "max_zoom": 20 + }, + { + "id": "Stamen.Watercolor", + "name": "Watercolor (by Stamen)", + "category": "osmbasedmap", + "url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png", + "attribution": { + "html": "Map tiles by Stamen Design, CC BY 3.0 — Map data {attribution.OpenStreetMap}" + }, + "min_zoom": 0, + "max_zoom": 20 + }, + { + "id": "CartoDB.Positron", + "name": "Positron (by CartoDB)", + "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png", + "attribution": { + "html": "CARTO" + }, + "max_zoom": 20, + "category": "osmbasedmap" + }, + { + "id": "CartoDB.PositronNoLabels", + "name": "Positron - no labels (by CartoDB)", + "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png", + "category": "osmbasedmap", + "attribution": { + "html": "CARTO" + }, + "max_zoom": 20 + }, + { + "id": "CartoDB.Voyager", + "name": "Voyager (by CartoDB)", + "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png", + "category": "osmbasedmap", + "attribution": { + "html": "CARTO" + }, + "max_zoom": 20 + }, + { + "id": "CartoDB.VoyagerNoLabels", + "name": "Voyager - no labels (by CartoDB)", + "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}.png", + "category": "osmbasedmap", + "attribution": { + "html": "CARTO" + }, + "max_zoom": 20 + }, + { + "id": "CartoDB.DarkMatter", + "name": "Dark Matter (by CartoDB)", + "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png", + "category": "osmbasedmap", + "attribution": { + "html": "CARTO" + }, + "max_zoom": 20 + }, + { + "id": "CartoDB.DarkMatterNoLabels", + "name": "Dark Matter - no labels (by CartoDB)", + "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png", + "category": "osmbasedmap", + "attribution": { + "html": "CARTO" + }, + "max_zoom": 20 + } + ] +} diff --git a/package-lock.json b/package-lock.json index bd84a33a34..2f1747ea64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.25.1", "license": "GPL-3.0-or-later", "dependencies": { + "@onsvisual/svelte-maps": "^1.1.6", "@rollup/plugin-typescript": "^11.0.0", "@turf/boolean-intersects": "^6.5.0", "@turf/buffer": "^6.5.0", @@ -37,6 +38,7 @@ "libphonenumber-js": "^1.10.8", "lz-string": "^1.4.4", "mangrove-reviews-typescript": "^1.1.0", + "maplibre-gl": "^2.4.0", "opening_hours": "^3.6.0", "osm-auth": "^1.0.2", "osmtogeojson": "^3.0.0-beta.5", @@ -1799,6 +1801,24 @@ "geojson-rewind": "geojson-rewind" } }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/mapbox-gl-supported": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz", + "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ==" + }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" + }, "node_modules/@mapbox/sphericalmercator": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@mapbox/sphericalmercator/-/sphericalmercator-1.2.0.tgz", @@ -1810,6 +1830,32 @@ "xyz": "bin/xyz.js" } }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", + "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" + }, + "node_modules/@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "dependencies": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1842,6 +1888,16 @@ "node": ">= 8" } }, + "node_modules/@onsvisual/svelte-maps": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@onsvisual/svelte-maps/-/svelte-maps-1.1.6.tgz", + "integrity": "sha512-qrt/Z7SvOLzPdz+q3vL6VEVQp3ayavmPE7Rqbtbhicq3JFx/l/1W2VS8ryHgFGXi5nuuZtAzxW7/p8ZM0L/VOw==", + "peerDependencies": { + "maplibre-gl": "^2.4.0", + "pmtiles": "^2.7.0", + "svelte": "^3.32.2" + } + }, "node_modules/@parcel/service-worker": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/@parcel/service-worker/-/service-worker-2.8.2.tgz", @@ -3572,8 +3628,7 @@ "node_modules/@types/geojson": { "version": "7946.0.10", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", - "devOptional": true + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", @@ -3642,6 +3697,21 @@ "integrity": "sha512-j6G1e8DULJx3ONf6NdR5JiR2ZY3K3PaaqiEuKYkLQO0Czfi1AzrtjfnfCROyWGeDd5IVMKCwsgSmMip9OWijow==", "dev": true }, + "node_modules/@types/mapbox__point-geometry": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.2.tgz", + "integrity": "sha512-D0lgCq+3VWV85ey1MZVkE8ZveyuvW5VAfuahVTQRpXFQTxw03SuIf1/K4UQ87MMIXVKzpFjXFiFMZzLj2kU+iA==" + }, + "node_modules/@types/mapbox__vector-tile": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.0.tgz", + "integrity": "sha512-kDwVreQO5V4c8yAxzZVQLE5tyWF+IPToAanloQaSnwfXmIcJ7cyOrv8z4Ft4y7PsLYmhWXmON8MBV8RX0Rgr8g==", + "dependencies": { + "@types/geojson": "*", + "@types/mapbox__point-geometry": "*", + "@types/pbf": "*" + } + }, "node_modules/@types/node": { "version": "18.11.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", @@ -3656,6 +3726,11 @@ "@types/node": "*" } }, + "node_modules/@types/pbf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.2.tgz", + "integrity": "sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ==" + }, "node_modules/@types/prompt-sync": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/prompt-sync/-/prompt-sync-4.2.0.tgz", @@ -4811,6 +4886,11 @@ "utrie": "^1.0.2" } }, + "node_modules/csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5975,6 +6055,11 @@ "quickselect": "^2.0.0" } }, + "node_modules/geojson-vt": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", + "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==" + }, "node_modules/geojson2svg": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/geojson2svg/-/geojson2svg-1.3.3.tgz", @@ -6037,6 +6122,11 @@ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "dev": true }, + "node_modules/gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -6076,6 +6166,30 @@ "process": "^0.11.10" } }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -6487,8 +6601,7 @@ "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/inquirer": { "version": "8.2.0", @@ -7144,6 +7257,14 @@ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-1.0.1.tgz", "integrity": "sha512-Y75c18KdvLKRmqHc0u2WUYud1vEj54i+8SNBxsowr6LJJsnNUJ8KK8cH7uHDpC5U66NNlieEzVxeWipZaYfN0w==" }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -7382,6 +7503,43 @@ "typescript": "^4.9.4" } }, + "node_modules/maplibre-gl": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-2.4.0.tgz", + "integrity": "sha512-csNFylzntPmHWidczfgCZpvbTSmhaWvLRj9e1ezUDBEPizGgshgm3ea1T5TCNEEBq0roauu7BPuRZjA3wO4KqA==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^2.0.1", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.5", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@types/geojson": "^7946.0.10", + "@types/mapbox__point-geometry": "^0.1.2", + "@types/mapbox__vector-tile": "^1.3.0", + "@types/pbf": "^3.0.2", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.4", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.4.3", + "global-prefix": "^3.0.0", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.2", + "quickselect": "^2.0.0", + "supercluster": "^7.1.5", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.3" + } + }, + "node_modules/maplibre-gl/node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -7667,6 +7825,11 @@ "resolved": "https://registry.npmjs.org/multigeojson/-/multigeojson-0.0.1.tgz", "integrity": "sha512-FbCR4K9xp+0lbcHmJk1TLjXW+l82VcEhDDIU7g3DWm47WyGSpuGX8lJx58pOPa61T0b1zQUJVjllPJ6eXe54lg==" }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -8195,6 +8358,21 @@ "pathe": "^1.0.0" } }, + "node_modules/pmtiles": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/pmtiles/-/pmtiles-2.7.0.tgz", + "integrity": "sha512-/0WERHBKCt9P8dlQaROQd1CE/J1sqYhdGkDgOROaxCjnpm/U+guWNNxZPPduUy6Wu7wQtPghA7sS4t0T18FKAA==", + "peer": true, + "dependencies": { + "fflate": "^0.7.3" + } + }, + "node_modules/pmtiles/node_modules/fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "peer": true + }, "node_modules/point-in-polygon": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", @@ -8220,6 +8398,11 @@ "node": ">=4" } }, + "node_modules/potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==" + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -9466,6 +9649,19 @@ "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.9.0.tgz", "integrity": "sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A==" }, + "node_modules/supercluster": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", + "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==", + "dependencies": { + "kdbush": "^3.0.0" + } + }, + "node_modules/supercluster/node_modules/kdbush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", + "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -9493,7 +9689,6 @@ "version": "3.55.1", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz", "integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==", - "dev": true, "engines": { "node": ">= 8" } @@ -11446,6 +11641,16 @@ "node": ">=0.4.0" } }, + "node_modules/vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "dependencies": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -13036,11 +13241,49 @@ "minimist": "^1.2.6" } }, + "@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==" + }, + "@mapbox/mapbox-gl-supported": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz", + "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ==" + }, + "@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" + }, "@mapbox/sphericalmercator": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@mapbox/sphericalmercator/-/sphericalmercator-1.2.0.tgz", "integrity": "sha512-ZTOuuwGuMOJN+HEmG/68bSEw15HHaMWmQ5gdTsWdWsjDe56K1kGvLOK6bOSC8gWgIvEO0w6un/2Gvv1q5hJSkQ==" }, + "@mapbox/tiny-sdf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", + "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==" + }, + "@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" + }, + "@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "requires": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -13064,6 +13307,12 @@ "fastq": "^1.6.0" } }, + "@onsvisual/svelte-maps": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@onsvisual/svelte-maps/-/svelte-maps-1.1.6.tgz", + "integrity": "sha512-qrt/Z7SvOLzPdz+q3vL6VEVQp3ayavmPE7Rqbtbhicq3JFx/l/1W2VS8ryHgFGXi5nuuZtAzxW7/p8ZM0L/VOw==", + "requires": {} + }, "@parcel/service-worker": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/@parcel/service-worker/-/service-worker-2.8.2.tgz", @@ -14417,8 +14666,7 @@ "@types/geojson": { "version": "7946.0.10", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", - "devOptional": true + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" }, "@types/istanbul-lib-coverage": { "version": "2.0.4", @@ -14486,6 +14734,21 @@ "integrity": "sha512-j6G1e8DULJx3ONf6NdR5JiR2ZY3K3PaaqiEuKYkLQO0Czfi1AzrtjfnfCROyWGeDd5IVMKCwsgSmMip9OWijow==", "dev": true }, + "@types/mapbox__point-geometry": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.2.tgz", + "integrity": "sha512-D0lgCq+3VWV85ey1MZVkE8ZveyuvW5VAfuahVTQRpXFQTxw03SuIf1/K4UQ87MMIXVKzpFjXFiFMZzLj2kU+iA==" + }, + "@types/mapbox__vector-tile": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.0.tgz", + "integrity": "sha512-kDwVreQO5V4c8yAxzZVQLE5tyWF+IPToAanloQaSnwfXmIcJ7cyOrv8z4Ft4y7PsLYmhWXmON8MBV8RX0Rgr8g==", + "requires": { + "@types/geojson": "*", + "@types/mapbox__point-geometry": "*", + "@types/pbf": "*" + } + }, "@types/node": { "version": "18.11.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", @@ -14500,6 +14763,11 @@ "@types/node": "*" } }, + "@types/pbf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.2.tgz", + "integrity": "sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ==" + }, "@types/prompt-sync": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/prompt-sync/-/prompt-sync-4.2.0.tgz", @@ -15382,6 +15650,11 @@ "utrie": "^1.0.2" } }, + "csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==" + }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -16288,6 +16561,11 @@ } } }, + "geojson-vt": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", + "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==" + }, "geojson2svg": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/geojson2svg/-/geojson2svg-1.3.3.tgz", @@ -16335,6 +16613,11 @@ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "dev": true }, + "gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -16365,6 +16648,26 @@ "process": "^0.11.10" } }, + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -16668,8 +16971,7 @@ "ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "inquirer": { "version": "8.2.0", @@ -17149,6 +17451,11 @@ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-1.0.1.tgz", "integrity": "sha512-Y75c18KdvLKRmqHc0u2WUYud1vEj54i+8SNBxsowr6LJJsnNUJ8KK8cH7uHDpC5U66NNlieEzVxeWipZaYfN0w==" }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, "kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -17335,6 +17642,44 @@ "typescript": "^4.9.4" } }, + "maplibre-gl": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-2.4.0.tgz", + "integrity": "sha512-csNFylzntPmHWidczfgCZpvbTSmhaWvLRj9e1ezUDBEPizGgshgm3ea1T5TCNEEBq0roauu7BPuRZjA3wO4KqA==", + "requires": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^2.0.1", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.5", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@types/geojson": "^7946.0.10", + "@types/mapbox__point-geometry": "^0.1.2", + "@types/mapbox__vector-tile": "^1.3.0", + "@types/pbf": "^3.0.2", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.4", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.4.3", + "global-prefix": "^3.0.0", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.2", + "quickselect": "^2.0.0", + "supercluster": "^7.1.5", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.3" + }, + "dependencies": { + "quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + } + } + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -17547,6 +17892,11 @@ "resolved": "https://registry.npmjs.org/multigeojson/-/multigeojson-0.0.1.tgz", "integrity": "sha512-FbCR4K9xp+0lbcHmJk1TLjXW+l82VcEhDDIU7g3DWm47WyGSpuGX8lJx58pOPa61T0b1zQUJVjllPJ6eXe54lg==" }, + "murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -17945,6 +18295,23 @@ "pathe": "^1.0.0" } }, + "pmtiles": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/pmtiles/-/pmtiles-2.7.0.tgz", + "integrity": "sha512-/0WERHBKCt9P8dlQaROQd1CE/J1sqYhdGkDgOROaxCjnpm/U+guWNNxZPPduUy6Wu7wQtPghA7sS4t0T18FKAA==", + "peer": true, + "requires": { + "fflate": "^0.7.3" + }, + "dependencies": { + "fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "peer": true + } + } + }, "point-in-polygon": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", @@ -17967,6 +18334,11 @@ "util-deprecate": "^1.0.2" } }, + "potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==" + }, "prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -18902,6 +19274,21 @@ "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.9.0.tgz", "integrity": "sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A==" }, + "supercluster": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", + "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==", + "requires": { + "kdbush": "^3.0.0" + }, + "dependencies": { + "kdbush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", + "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -18919,8 +19306,7 @@ "svelte": { "version": "3.55.1", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz", - "integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==", - "dev": true + "integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==" }, "svelte-check": { "version": "3.0.3", @@ -20360,6 +20746,16 @@ } } }, + "vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "requires": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, "w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", diff --git a/package.json b/package.json index 3fa44de645..a224b3ac7d 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "not op_mini all" ], "dependencies": { + "@onsvisual/svelte-maps": "^1.1.6", "@rollup/plugin-typescript": "^11.0.0", "@turf/boolean-intersects": "^6.5.0", "@turf/buffer": "^6.5.0", @@ -89,6 +90,7 @@ "libphonenumber-js": "^1.10.8", "lz-string": "^1.4.4", "mangrove-reviews-typescript": "^1.1.0", + "maplibre-gl": "^2.4.0", "opening_hours": "^3.6.0", "osm-auth": "^1.0.2", "osmtogeojson": "^3.0.0-beta.5", diff --git a/test.ts b/test.ts index 7c1a0d9bef..80b3de758e 100644 --- a/test.ts +++ b/test.ts @@ -1,25 +1,72 @@ -import ContactLink from "./UI/BigComponents/ContactLink.svelte" import SvelteUIElement from "./UI/Base/SvelteUIElement" -import { Utils } from "./Utils" -import List from "./UI/Base/List" +import MaplibreMap from "./UI/Map/MaplibreMap.svelte" +import { Store, Stores, UIEventSource } from "./Logic/UIEventSource" +import { MapLibreAdaptor } from "./UI/Map/MapLibreAdaptor" +import { + EditorLayerIndexProperties, + RasterLayerPolygon, + RasterLayerProperties, +} from "./Models/RasterLayers" +import type { Map as MlMap } from "maplibre-gl" +import { AvailableRasterLayers } from "./Models/RasterLayers" +import Loc from "./Models/Loc" +import { BBox } from "./Logic/BBox" import { GeoOperations } from "./Logic/GeoOperations" -import { Tiles } from "./Models/TileRange" -import { Stores } from "./Logic/UIEventSource" +import RasterLayerPicker from "./UI/Map/RasterLayerPicker.svelte" +import BackgroundLayerResetter from "./Logic/Actors/BackgroundLayerResetter" async function main() { - const location: [number, number] = [3.21, 51.2] - const t = Tiles.embedded_tile(location[1], location[0], 6) - const url = `https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/community_index/tile_${t.z}_${t.x}_${t.y}.geojson` - const be = Stores.FromPromise(Utils.downloadJson(url)).mapD( - (data) => data.features.find((f) => GeoOperations.inside(location, f)).properties + const mlmap = new UIEventSource(undefined) + const locationControl = new UIEventSource({ + zoom: 14, + lat: 51.1, + lon: 3.1, + }) + new SvelteUIElement(MaplibreMap, { + map: mlmap, + }) + .SetClass("border border-black") + .SetStyle("height: 50vh; width: 90%; margin: 1%") + .AttachTo("maindiv") + const bg = new UIEventSource(undefined) + new MapLibreAdaptor(mlmap, { + backgroundLayer: bg, + locationControl, + }) + + const availableLayersBboxes = Stores.ListStabilized( + locationControl.mapD((loc) => { + const lonlat: [number, number] = [loc.lon, loc.lat] + return AvailableRasterLayers.EditorLayerIndex.filter((eliPolygon) => + BBox.get(eliPolygon).contains(lonlat) + ) + }) ) - new SvelteUIElement(ContactLink, { country: be }).AttachTo("maindiv") - /* - const links = data.features - .filter((f) => GeoOperations.inside(location, f)) - .map((f) => new SvelteUIElement(ContactLink, { country: f.properties })) - new List(links).AttachTo("maindiv") - //*/ + const availableLayers: Store = Stores.ListStabilized( + availableLayersBboxes.map((eliPolygons) => { + const loc = locationControl.data + const lonlat: [number, number] = [loc.lon, loc.lat] + const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => { + if (eliPolygon.geometry === null) { + return true // global ELI-layer + } + return GeoOperations.inside(lonlat, eliPolygon) + }) + matching.unshift(AvailableRasterLayers.osmCarto) + matching.push(...AvailableRasterLayers.globalLayers) + return matching + }) + ) + + availableLayers.map((a) => + console.log( + "Availabe layers at current location:", + a.map((al) => al.properties.id) + ) + ) + + new BackgroundLayerResetter(bg, availableLayers) + new SvelteUIElement(RasterLayerPicker, { availableLayers, value: bg }).AttachTo("extradiv") } main().then((_) => {}) From 1b3609b13f7c739b7cd0a55852a5a4324e109636 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 23 Mar 2023 00:58:21 +0100 Subject: [PATCH 002/257] refactoring(maplibre): add pointRendering --- Logic/BBox.ts | 8 ++ Logic/State/MapState.ts | 6 - Models/RasterLayers.ts | 31 ++++++ Models/ThemeConfig/PointRenderingConfig.ts | 30 ++--- UI/BigComponents/BackgroundMapSwitch.ts | 2 - UI/Input/ValidatedTextField.ts | 2 - UI/Map/MapLibreAdaptor.ts | 98 +++++++++------- UI/Map/ShowDataLayer.ts | 108 ++++++++++++++++++ UI/ShowDataLayer/ShowDataLayerOptions.ts | 30 ++++- test.ts | 123 +++++++++++++-------- 10 files changed, 316 insertions(+), 122 deletions(-) create mode 100644 UI/Map/ShowDataLayer.ts diff --git a/Logic/BBox.ts b/Logic/BBox.ts index 9f1cbceab4..8e8bc14c6b 100644 --- a/Logic/BBox.ts +++ b/Logic/BBox.ts @@ -186,6 +186,14 @@ export class BBox { ] } + toLngLat(): [[number, number], [number, number]] { + return [ + [this.minLon, this.minLat], + [this.maxLon, this.maxLat], + ] + } + + public asGeoJson(properties: T): Feature { return { type: "Feature", diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts index 121e82ff9e..59b1770442 100644 --- a/Logic/State/MapState.ts +++ b/Logic/State/MapState.ts @@ -1,8 +1,6 @@ import UserRelatedState from "./UserRelatedState" import { Store, Stores, UIEventSource } from "../UIEventSource" -import BaseLayer from "../../Models/BaseLayer" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import AvailableBaseLayers from "../Actors/AvailableBaseLayers" import Attribution from "../../UI/BigComponents/Attribution" import Minimap, { MinimapObj } from "../../UI/Base/Minimap" import { Tiles } from "../../Models/TileRange" @@ -43,10 +41,6 @@ export default class MapState extends UserRelatedState { The leaflet instance of the big basemap */ public leafletMap = new UIEventSource(undefined, "leafletmap") - /** - * A list of currently available background layers - */ - public availableBackgroundLayers: Store /** * The current background layer diff --git a/Models/RasterLayers.ts b/Models/RasterLayers.ts index 602af9c3b0..84d9fc53e4 100644 --- a/Models/RasterLayers.ts +++ b/Models/RasterLayers.ts @@ -2,6 +2,8 @@ import { Feature, Polygon } from "geojson" import * as editorlayerindex from "../assets/editor-layer-index.json" import * as globallayers from "../assets/global-raster-layers.json" import { BBox } from "../Logic/BBox" +import { Store, Stores } from "../Logic/UIEventSource" +import { GeoOperations } from "../Logic/GeoOperations" export class AvailableRasterLayers { public static EditorLayerIndex: (Feature & @@ -33,6 +35,35 @@ export class AvailableRasterLayers { properties: AvailableRasterLayers.osmCartoProperties, geometry: BBox.global.asGeometry(), } + + public static layersAvailableAt( + location: Store<{ lon: number; lat: number }> + ): Store { + const availableLayersBboxes = Stores.ListStabilized( + location.mapD((loc) => { + const lonlat: [number, number] = [loc.lon, loc.lat] + return AvailableRasterLayers.EditorLayerIndex.filter((eliPolygon) => + BBox.get(eliPolygon).contains(lonlat) + ) + }) + ) + const available = Stores.ListStabilized( + availableLayersBboxes.map((eliPolygons) => { + const loc = location.data + const lonlat: [number, number] = [loc.lon, loc.lat] + const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => { + if (eliPolygon.geometry === null) { + return true // global ELI-layer + } + return GeoOperations.inside(lonlat, eliPolygon) + }) + matching.unshift(AvailableRasterLayers.osmCarto) + matching.push(...AvailableRasterLayers.globalLayers) + return matching + }) + ) + return available + } } export class RasterLayerUtils { diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts index 8c3712f1fc..d6a3317df3 100644 --- a/Models/ThemeConfig/PointRenderingConfig.ts +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -5,12 +5,13 @@ import { TagUtils } from "../../Logic/Tags/TagUtils" import { Utils } from "../../Utils" import Svg from "../../Svg" import WithContextLoader from "./WithContextLoader" -import { UIEventSource } from "../../Logic/UIEventSource" +import { Store } from "../../Logic/UIEventSource" import BaseUIElement from "../../UI/BaseUIElement" import { FixedUiElement } from "../../UI/Base/FixedUiElement" import Img from "../../UI/Base/Img" import Combine from "../../UI/Base/Combine" import { VariableUiElement } from "../../UI/Base/VariableUIElement" +import { OsmTags } from "../OsmFeature" export default class PointRenderingConfig extends WithContextLoader { private static readonly allowed_location_codes = new Set([ @@ -164,7 +165,7 @@ export default class PointRenderingConfig extends WithContextLoader { return PointRenderingConfig.FromHtmlMulti(htmlDefs, rotation, false, defaultPin) } - public GetSimpleIcon(tags: UIEventSource): BaseUIElement { + public GetSimpleIcon(tags: Store): BaseUIElement { const self = this if (this.icon === undefined) { return undefined @@ -175,7 +176,7 @@ export default class PointRenderingConfig extends WithContextLoader { } public GenerateLeafletStyle( - tags: UIEventSource, + tags: Store, clickable: boolean, options?: { noSize?: false | boolean @@ -183,11 +184,7 @@ export default class PointRenderingConfig extends WithContextLoader { } ): { html: BaseUIElement - iconSize: [number, number] iconAnchor: [number, number] - popupAnchor: [number, number] - iconUrl: string - className: string } { function num(str, deflt = 40) { const n = Number(str) @@ -211,20 +208,21 @@ export default class PointRenderingConfig extends WithContextLoader { let iconH = num(iconSize[1]) const mode = iconSize[2]?.trim()?.toLowerCase() ?? "center" - let anchorW = iconW / 2 + // in MapLibre, the offset is relative to the _center_ of the object, with left = [-x, 0] and up = [0,-y] + let anchorW = 0 let anchorH = iconH / 2 if (mode === "left") { - anchorW = 0 + anchorW = -iconW / 2 } if (mode === "right") { - anchorW = iconW + anchorW = iconW / 2 } if (mode === "top") { - anchorH = 0 + anchorH = -iconH / 2 } if (mode === "bottom") { - anchorH = iconH + anchorH = iconH / 2 } const icon = this.GetSimpleIcon(tags) @@ -264,15 +262,11 @@ export default class PointRenderingConfig extends WithContextLoader { } return { html: htmlEl, - iconSize: [iconW, iconH], iconAnchor: [anchorW, anchorH], - popupAnchor: [0, 3 - anchorH], - iconUrl: undefined, - className: clickable ? "leaflet-div-icon" : "leaflet-div-icon unclickable", } } - private GetBadges(tags: UIEventSource): BaseUIElement { + private GetBadges(tags: Store): BaseUIElement { if (this.iconBadges.length === 0) { return undefined } @@ -304,7 +298,7 @@ export default class PointRenderingConfig extends WithContextLoader { ).SetClass("absolute bottom-0 right-1/3 h-1/2 w-0") } - private GetLabel(tags: UIEventSource): BaseUIElement { + private GetLabel(tags: Store): BaseUIElement { if (this.label === undefined) { return undefined } diff --git a/UI/BigComponents/BackgroundMapSwitch.ts b/UI/BigComponents/BackgroundMapSwitch.ts index 47ff1f0eef..0ee831773f 100644 --- a/UI/BigComponents/BackgroundMapSwitch.ts +++ b/UI/BigComponents/BackgroundMapSwitch.ts @@ -3,8 +3,6 @@ import { UIEventSource } from "../../Logic/UIEventSource" import Loc from "../../Models/Loc" import Svg from "../../Svg" import Toggle from "../Input/Toggle" -import BaseLayer from "../../Models/BaseLayer" -import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" import BaseUIElement from "../BaseUIElement" import { GeoOperations } from "../../Logic/GeoOperations" import Hotkeys from "../Base/Hotkeys" diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index f24d8548b2..45c57fb3b5 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -18,14 +18,12 @@ import { Unit } from "../../Models/Unit" import { FixedInputElement } from "./FixedInputElement" import WikidataSearchBox from "../Wikipedia/WikidataSearchBox" import Wikidata from "../../Logic/Web/Wikidata" -import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" import Table from "../Base/Table" import Combine from "../Base/Combine" import Title from "../Base/Title" import InputElementMap from "./InputElementMap" import Translations from "../i18n/Translations" import { Translation } from "../i18n/Translation" -import BaseLayer from "../../Models/BaseLayer" import Locale from "../i18n/Locale" export class TextFieldDef { diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index f57b076b1a..682ab9f70e 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -1,65 +1,81 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" import type { Map as MLMap } from "maplibre-gl" -import { - EditorLayerIndexProperties, - RasterLayerPolygon, - RasterLayerProperties, -} from "../../Models/RasterLayers" +import { RasterLayerPolygon, RasterLayerProperties } from "../../Models/RasterLayers" import { Utils } from "../../Utils" -import Loc from "../../Models/Loc" +import { BBox } from "../../Logic/BBox" -export class MapLibreAdaptor { +export interface MapState { + readonly location: UIEventSource<{ lon: number; lat: number }> + readonly zoom: UIEventSource + readonly bounds: Store + readonly rasterLayer: UIEventSource +} +export class MapLibreAdaptor implements MapState { private readonly _maplibreMap: Store - private readonly _backgroundLayer?: Store - private _currentRasterLayer: string = undefined + readonly location: UIEventSource<{ lon: number; lat: number }> + readonly zoom: UIEventSource + readonly bounds: Store + readonly rasterLayer: UIEventSource + private readonly _bounds: UIEventSource - constructor( - maplibreMap: Store, - state?: { - // availableBackgroundLayers: Store - /** - * The current background layer - */ - readonly backgroundLayer?: Store - readonly locationControl?: UIEventSource - } - ) { + /** + * Used for internal bookkeeping (to remove a rasterLayer when done loading) + * @private + */ + private _currentRasterLayer: string + constructor(maplibreMap: Store, state?: Partial>) { this._maplibreMap = maplibreMap - this._backgroundLayer = state.backgroundLayer + + this.location = state?.location ?? new UIEventSource({ lon: 0, lat: 0 }) + this.zoom = state?.zoom ?? new UIEventSource(1) + this._bounds = new UIEventSource(BBox.global) + this.bounds = this._bounds + this.rasterLayer = + state?.rasterLayer ?? new UIEventSource(undefined) const self = this - this._backgroundLayer?.addCallback((_) => self.setBackground()) - maplibreMap.addCallbackAndRunD((map) => { map.on("load", () => { self.setBackground() }) - if (state.locationControl) { - self.MoveMapToCurrentLoc(state.locationControl.data) - map.on("moveend", () => { - const dt = state.locationControl.data - dt.lon = map.getCenter().lng - dt.lat = map.getCenter().lat - dt.zoom = map.getZoom() - state.locationControl.ping() - }) - } + self.MoveMapToCurrentLoc(this.location.data) + self.SetZoom(this.zoom.data) + map.on("moveend", () => { + const dt = this.location.data + dt.lon = map.getCenter().lng + dt.lat = map.getCenter().lat + this.location.ping() + this.zoom.setData(map.getZoom()) + }) }) - state.locationControl.addCallbackAndRunD((loc) => { + this.rasterLayer.addCallback((_) => + self.setBackground().catch((e) => { + console.error("Could not set background") + }) + ) + + this.location.addCallbackAndRunD((loc) => { self.MoveMapToCurrentLoc(loc) }) + this.zoom.addCallbackAndRunD((z) => self.SetZoom(z)) } - - private MoveMapToCurrentLoc(loc: Loc) { + private SetZoom(z: number) { + const map = this._maplibreMap.data + if (map === undefined || z === undefined) { + return + } + if (map.getZoom() !== z) { + map.setZoom(z) + } + } + private MoveMapToCurrentLoc(loc: { lat: number; lon: number }) { const map = this._maplibreMap.data if (map === undefined || loc === undefined) { return } - if (map.getZoom() !== loc.zoom) { - map.setZoom(loc.zoom) - } + const center = map.getCenter() if (center.lng !== loc.lon || center.lat !== loc.lat) { map.setCenter({ lng: loc.lon, lat: loc.lat }) @@ -120,14 +136,14 @@ export class MapLibreAdaptor { if (map === undefined) { return } - const background: RasterLayerProperties = this._backgroundLayer?.data?.properties + const background: RasterLayerProperties = this.rasterLayer?.data?.properties if (background !== undefined && this._currentRasterLayer === background.id) { // already the correct background layer, nothing to do return } await this.awaitStyleIsLoaded() - if (background !== this._backgroundLayer?.data?.properties) { + if (background !== this.rasterLayer?.data?.properties) { // User selected another background in the meantime... abort return } diff --git a/UI/Map/ShowDataLayer.ts b/UI/Map/ShowDataLayer.ts new file mode 100644 index 0000000000..904fa59d0f --- /dev/null +++ b/UI/Map/ShowDataLayer.ts @@ -0,0 +1,108 @@ +import { ImmutableStore, Store } from "../../Logic/UIEventSource" +import type { Map as MlMap } from "maplibre-gl" +import { Marker } from "maplibre-gl" +import { ShowDataLayerOptions } from "../ShowDataLayer/ShowDataLayerOptions" +import { GeoOperations } from "../../Logic/GeoOperations" +import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import PointRenderingConfig from "../../Models/ThemeConfig/PointRenderingConfig" +import { OsmFeature, OsmTags } from "../../Models/OsmFeature" +import FeatureSource from "../../Logic/FeatureSource/FeatureSource" +import { BBox } from "../../Logic/BBox" + +class PointRenderingLayer { + private readonly _config: PointRenderingConfig + private readonly _fetchStore?: (id: string) => Store + private readonly _map: MlMap + + constructor( + map: MlMap, + features: FeatureSource, + config: PointRenderingConfig, + fetchStore?: (id: string) => Store + ) { + this._config = config + this._map = map + this._fetchStore = fetchStore + const cache: Map = new Map() + const self = this + features.features.addCallbackAndRunD((features) => { + const unseenKeys = new Set(cache.keys()) + for (const { feature } of features) { + const id = feature.properties.id + unseenKeys.delete(id) + const loc = GeoOperations.centerpointCoordinates(feature) + if (cache.has(id)) { + console.log("Not creating a marker for ", id) + const cached = cache.get(id) + const oldLoc = cached.getLngLat() + console.log("OldLoc vs newLoc", oldLoc, loc) + if (loc[0] !== oldLoc.lng && loc[1] !== oldLoc.lat) { + cached.setLngLat(loc) + console.log("MOVED") + } + continue + } + + console.log("Creating a marker for ", id) + const marker = self.addPoint(feature) + cache.set(id, marker) + } + + for (const unseenKey of unseenKeys) { + cache.get(unseenKey).remove() + cache.delete(unseenKey) + } + }) + } + + private addPoint(feature: OsmFeature): Marker { + let store: Store + if (this._fetchStore) { + store = this._fetchStore(feature.properties.id) + } else { + store = new ImmutableStore(feature.properties) + } + const { html, iconAnchor } = this._config.GenerateLeafletStyle(store, true) + html.SetClass("marker") + const el = html.ConstructElement() + + el.addEventListener("click", function () { + window.alert("Hello world!") + }) + + return new Marker(el) + .setLngLat(GeoOperations.centerpointCoordinates(feature)) + .setOffset(iconAnchor) + .addTo(this._map) + } +} + +export class ShowDataLayer { + private readonly _map: Store + private _options: ShowDataLayerOptions & { layer: LayerConfig } + + constructor(map: Store, options: ShowDataLayerOptions & { layer: LayerConfig }) { + this._map = map + this._options = options + const self = this + map.addCallbackAndRunD((map) => self.initDrawFeatures(map)) + } + + private initDrawFeatures(map: MlMap) { + for (const pointRenderingConfig of this._options.layer.mapRendering) { + new PointRenderingLayer( + map, + this._options.features, + pointRenderingConfig, + this._options.fetchStore + ) + } + if (this._options.zoomToFeatures) { + const features = this._options.features.features.data + const bbox = BBox.bboxAroundAll(features.map((f) => BBox.get(f.feature))) + map.fitBounds(bbox.toLngLat(), { + padding: { top: 10, bottom: 10, left: 10, right: 10 }, + }) + } + } +} diff --git a/UI/ShowDataLayer/ShowDataLayerOptions.ts b/UI/ShowDataLayer/ShowDataLayerOptions.ts index 1b5832d2c4..9a66d0d494 100644 --- a/UI/ShowDataLayer/ShowDataLayerOptions.ts +++ b/UI/ShowDataLayer/ShowDataLayerOptions.ts @@ -3,13 +3,35 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" import { ElementStorage } from "../../Logic/ElementStorage" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import ScrollableFullScreen from "../Base/ScrollableFullScreen" +import { OsmTags } from "../../Models/OsmFeature" export interface ShowDataLayerOptions { + /** + * Features to show + */ features: FeatureSource + /** + * Indication of the current selected element; overrides some filters + */ selectedElement?: UIEventSource - leafletMap: Store - popup?: undefined | ((tags: UIEventSource, layer: LayerConfig) => ScrollableFullScreen) + /** + * What popup to build when a feature is selected + */ + buildPopup?: + | undefined + | ((tags: UIEventSource, layer: LayerConfig) => ScrollableFullScreen) + + /** + * If set, zoom to the features when initially loaded and when they are changed + */ zoomToFeatures?: false | boolean - doShowLayer?: Store - state?: { allElements?: ElementStorage } + /** + * Toggles the layer on/off + */ + doShowLayer?: Store + + /** + * Function which fetches the relevant store + */ + fetchStore?: (id: string) => Store } diff --git a/test.ts b/test.ts index 80b3de758e..23dde7df0b 100644 --- a/test.ts +++ b/test.ts @@ -1,24 +1,23 @@ import SvelteUIElement from "./UI/Base/SvelteUIElement" import MaplibreMap from "./UI/Map/MaplibreMap.svelte" -import { Store, Stores, UIEventSource } from "./Logic/UIEventSource" +import { ImmutableStore, UIEventSource } from "./Logic/UIEventSource" import { MapLibreAdaptor } from "./UI/Map/MapLibreAdaptor" -import { - EditorLayerIndexProperties, - RasterLayerPolygon, - RasterLayerProperties, -} from "./Models/RasterLayers" +import { AvailableRasterLayers, RasterLayerPolygon } from "./Models/RasterLayers" import type { Map as MlMap } from "maplibre-gl" -import { AvailableRasterLayers } from "./Models/RasterLayers" -import Loc from "./Models/Loc" -import { BBox } from "./Logic/BBox" -import { GeoOperations } from "./Logic/GeoOperations" import RasterLayerPicker from "./UI/Map/RasterLayerPicker.svelte" import BackgroundLayerResetter from "./Logic/Actors/BackgroundLayerResetter" - +import { ShowDataLayer } from "./UI/Map/ShowDataLayer" +import StaticFeatureSource from "./Logic/FeatureSource/Sources/StaticFeatureSource" +import { Layer } from "leaflet" +import LayerConfig from "./Models/ThemeConfig/LayerConfig" +import * as bench from "./assets/generated/layers/bench.json" +import { Utils } from "./Utils" +import SimpleFeatureSource from "./Logic/FeatureSource/Sources/SimpleFeatureSource" +import { FilterState } from "./Models/FilteredLayer" +import { FixedUiElement } from "./UI/Base/FixedUiElement" async function main() { const mlmap = new UIEventSource(undefined) - const locationControl = new UIEventSource({ - zoom: 14, + const location = new UIEventSource<{ lon: number; lat: number }>({ lat: 51.1, lon: 3.1, }) @@ -29,44 +28,70 @@ async function main() { .SetStyle("height: 50vh; width: 90%; margin: 1%") .AttachTo("maindiv") const bg = new UIEventSource(undefined) - new MapLibreAdaptor(mlmap, { - backgroundLayer: bg, - locationControl, + const mla = new MapLibreAdaptor(mlmap, { + rasterLayer: bg, + location, }) - const availableLayersBboxes = Stores.ListStabilized( - locationControl.mapD((loc) => { - const lonlat: [number, number] = [loc.lon, loc.lat] - return AvailableRasterLayers.EditorLayerIndex.filter((eliPolygon) => - BBox.get(eliPolygon).contains(lonlat) - ) - }) - ) - const availableLayers: Store = Stores.ListStabilized( - availableLayersBboxes.map((eliPolygons) => { - const loc = locationControl.data - const lonlat: [number, number] = [loc.lon, loc.lat] - const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => { - if (eliPolygon.geometry === null) { - return true // global ELI-layer - } - return GeoOperations.inside(lonlat, eliPolygon) - }) - matching.unshift(AvailableRasterLayers.osmCarto) - matching.push(...AvailableRasterLayers.globalLayers) - return matching - }) - ) - - availableLayers.map((a) => - console.log( - "Availabe layers at current location:", - a.map((al) => al.properties.id) - ) - ) - - new BackgroundLayerResetter(bg, availableLayers) - new SvelteUIElement(RasterLayerPicker, { availableLayers, value: bg }).AttachTo("extradiv") + const features = new UIEventSource([ + { + feature: { + type: "Feature", + properties: { + hello: "world", + id: "" + 1, + }, + geometry: { + type: "Point", + coordinates: [3.1, 51.2], + }, + }, + freshness: new Date(), + }, + ]) + const layer = new LayerConfig(bench) + const options = { + zoomToFeatures: false, + features: new SimpleFeatureSource( + { + layerDef: layer, + isDisplayed: new UIEventSource(true), + appliedFilters: new UIEventSource>(undefined), + }, + 0, + features + ), + layer, + } + new ShowDataLayer(mlmap, options) + mla.zoom.set(9) + mla.location.set({ lon: 3.1, lat: 51.1 }) + const availableLayers = AvailableRasterLayers.layersAvailableAt(location) + // new BackgroundLayerResetter(bg, availableLayers) + // new SvelteUIElement(RasterLayerPicker, { availableLayers, value: bg }).AttachTo("extradiv") + for (let i = 0; i <= 10; i++) { + await Utils.waitFor(1000) + features.ping() + new FixedUiElement("> " + (5 - i)).AttachTo("extradiv") + } + options.zoomToFeatures = false + features.setData([ + { + feature: { + type: "Feature", + properties: { + hello: "world", + id: "" + 1, + }, + geometry: { + type: "Point", + coordinates: [3.103, 51.10003], + }, + }, + freshness: new Date(), + }, + ]) + new FixedUiElement("> OK").AttachTo("extradiv") } main().then((_) => {}) From 231d67361eb6385e4d96ac237c7568b57eceafa7 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 23 Mar 2023 01:42:47 +0100 Subject: [PATCH 003/257] Refactoring(maplibre): remove 'freshness' and 'name' from FeatureSource to simplify the code --- Logic/Actors/OverpassFeatureSource.ts | 7 +- .../Actors/MetaTagRecalculator.ts | 2 +- .../RegisteringAllFromFeatureSourceActor.ts | 13 ++-- .../Actors/SaveTileToLocalStorageActor.ts | 5 +- Logic/FeatureSource/FeaturePipeline.ts | 9 +-- Logic/FeatureSource/FeatureSource.ts | 11 +-- .../PerLayerFeatureSourceSplitter.ts | 5 +- .../Sources/ChangeGeometryApplicator.ts | 15 ++-- .../Sources/FeatureSourceMerger.ts | 30 +++----- .../Sources/FilteringFeatureSource.ts | 27 ++----- Logic/FeatureSource/Sources/GeoJsonSource.ts | 9 ++- .../NewGeometryFromChangesFeatureSource.ts | 13 +--- .../Sources/RememberingSource.ts | 11 +-- .../RenderingMultiPlexerFeatureSource.ts | 10 +-- .../Sources/SimpleFeatureSource.ts | 13 +--- .../Sources/StaticFeatureSource.ts | 75 ++++++------------- .../FullNodeDatabaseSource.ts | 8 +- .../TiledFeatureSource/OsmFeatureSource.ts | 2 +- .../TiledFeatureSource/TiledFeatureSource.ts | 9 ++- Logic/MetaTagging.ts | 15 ++-- Logic/SimpleMetaTagger.ts | 29 +++---- Logic/State/FeaturePipelineState.ts | 4 +- Logic/State/MapState.ts | 57 +++++++------- Models/OsmFeature.ts | 2 +- .../CompareToAlreadyExistingNotes.ts | 2 +- UI/ImportFlow/ConflationChecker.ts | 6 +- UI/ImportFlow/MapPreview.ts | 6 +- UI/Input/LocationInput.ts | 10 +-- UI/NewPoint/ConfirmLocationOfPoint.ts | 18 ++--- test.ts | 7 +- 30 files changed, 161 insertions(+), 269 deletions(-) diff --git a/Logic/Actors/OverpassFeatureSource.ts b/Logic/Actors/OverpassFeatureSource.ts index 7560e6c03a..bd2e9c4b0e 100644 --- a/Logic/Actors/OverpassFeatureSource.ts +++ b/Logic/Actors/OverpassFeatureSource.ts @@ -13,15 +13,15 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import Constants from "../../Models/Constants" import TileFreshnessCalculator from "../FeatureSource/TileFreshnessCalculator" import { Tiles } from "../../Models/TileRange" +import { Feature } from "geojson" export default class OverpassFeatureSource implements FeatureSource { public readonly name = "OverpassFeatureSource" /** - * The last loaded features of the geojson + * The last loaded features, as geojson */ - public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = - new UIEventSource(undefined) + public readonly features: UIEventSource = new UIEventSource(undefined) public readonly runningQuery: UIEventSource = new UIEventSource(false) public readonly timeout: UIEventSource = new UIEventSource(0) @@ -243,7 +243,6 @@ export default class OverpassFeatureSource implements FeatureSource { data.features.forEach((feature) => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature( feature, - date, undefined, this.state ) diff --git a/Logic/FeatureSource/Actors/MetaTagRecalculator.ts b/Logic/FeatureSource/Actors/MetaTagRecalculator.ts index 982939d4a6..165279b92c 100644 --- a/Logic/FeatureSource/Actors/MetaTagRecalculator.ts +++ b/Logic/FeatureSource/Actors/MetaTagRecalculator.ts @@ -101,7 +101,7 @@ export default class MetaTagRecalculator { } } - public registerSource(source: FeatureSourceForLayer & Tiled, recalculateOnEveryChange = false) { + public registerSource(source: FeatureSourceForLayer & Tiled) { if (source === undefined) { return } diff --git a/Logic/FeatureSource/Actors/RegisteringAllFromFeatureSourceActor.ts b/Logic/FeatureSource/Actors/RegisteringAllFromFeatureSourceActor.ts index 01ad3917b5..756c11da3b 100644 --- a/Logic/FeatureSource/Actors/RegisteringAllFromFeatureSourceActor.ts +++ b/Logic/FeatureSource/Actors/RegisteringAllFromFeatureSourceActor.ts @@ -1,20 +1,19 @@ -import FeatureSource from "../FeatureSource" -import { Store } from "../../UIEventSource" -import { ElementStorage } from "../../ElementStorage" +import FeatureSource from "../FeatureSource"; +import { Store } from "../../UIEventSource"; +import { ElementStorage } from "../../ElementStorage"; +import { Feature } from "geojson"; /** * Makes sure that every feature is added to the ElementsStorage, so that the tags-eventsource can be retrieved */ export default class RegisteringAllFromFeatureSourceActor { - public readonly features: Store<{ feature: any; freshness: Date }[]> - public readonly name + public readonly features: Store constructor(source: FeatureSource, allElements: ElementStorage) { this.features = source.features - this.name = "RegisteringSource of " + source.name this.features.addCallbackAndRunD((features) => { for (const feature of features) { - allElements.addOrGetElement(feature.feature) + allElements.addOrGetElement( feature) } }) } diff --git a/Logic/FeatureSource/Actors/SaveTileToLocalStorageActor.ts b/Logic/FeatureSource/Actors/SaveTileToLocalStorageActor.ts index 12ef0e5742..9cf1b1de51 100644 --- a/Logic/FeatureSource/Actors/SaveTileToLocalStorageActor.ts +++ b/Logic/FeatureSource/Actors/SaveTileToLocalStorageActor.ts @@ -7,6 +7,7 @@ import { BBox } from "../../BBox" import SimpleFeatureSource from "../Sources/SimpleFeatureSource" import FilteredLayer from "../../../Models/FilteredLayer" import Loc from "../../../Models/Loc" +import { Feature } from "geojson" /*** * Saves all the features that are passed in to localstorage, so they can be retrieved on the next run @@ -82,7 +83,7 @@ export default class SaveTileToLocalStorageActor { continue } loadedTiles.add(key) - this.GetIdb(key).then((features: { feature: any; freshness: Date }[]) => { + this.GetIdb(key).then((features: Feature[]) => { if (features === undefined) { return } @@ -90,7 +91,7 @@ export default class SaveTileToLocalStorageActor { const src = new SimpleFeatureSource( self._flayer, key, - new UIEventSource<{ feature: any; freshness: Date }[]>(features) + new UIEventSource(features) ) registerTile(src) }) diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index e398294d65..edfac8fbdc 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -136,7 +136,7 @@ export default class FeaturePipeline { // Passthrough to passed function, except that it registers as well handleFeatureSource(src) src.features.addCallbackAndRunD((fs) => { - fs.forEach((ff) => state.allElements.addOrGetElement(ff.feature)) + fs.forEach((ff) => state.allElements.addOrGetElement(ff)) }) } @@ -422,7 +422,7 @@ export default class FeaturePipeline { } return TileHierarchyTools.getTiles(requestedHierarchy, bbox) .filter((featureSource) => featureSource.features?.data !== undefined) - .map((featureSource) => featureSource.features.data.map((fs) => fs.feature)) + .map((featureSource) => featureSource.features.data) } public GetTilesPerLayerWithin( @@ -639,10 +639,7 @@ export default class FeaturePipeline { * Inject a new point */ InjectNewPoint(geojson) { - this.newGeometryHandler.features.data.push({ - feature: geojson, - freshness: new Date(), - }) + this.newGeometryHandler.features.data.push(geojson) this.newGeometryHandler.features.ping() } } diff --git a/Logic/FeatureSource/FeatureSource.ts b/Logic/FeatureSource/FeatureSource.ts index c5460ff685..243b8efd96 100644 --- a/Logic/FeatureSource/FeatureSource.ts +++ b/Logic/FeatureSource/FeatureSource.ts @@ -1,15 +1,10 @@ -import { Store, UIEventSource } from "../UIEventSource" +import { Store } from "../UIEventSource" import FilteredLayer from "../../Models/FilteredLayer" import { BBox } from "../BBox" -import { Feature, Geometry } from "@turf/turf" -import { OsmFeature } from "../../Models/OsmFeature" +import { Feature } from "geojson" export default interface FeatureSource { - features: Store<{ feature: OsmFeature; freshness: Date }[]> - /** - * Mainly used for debuging - */ - name: string + features: Store } export interface Tiled { diff --git a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts index 6b6bfd330b..ef92543a7f 100644 --- a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts +++ b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts @@ -2,6 +2,7 @@ import FeatureSource, { FeatureSourceForLayer, Tiled } from "./FeatureSource" import { Store } from "../UIEventSource" import FilteredLayer from "../../Models/FilteredLayer" import SimpleFeatureSource from "./Sources/SimpleFeatureSource" +import { Feature } from "geojson" /** * In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled) @@ -32,7 +33,7 @@ export default class PerLayerFeatureSourceSplitter { // We try to figure out (for each feature) in which feature store it should be saved. // Note that this splitter is only run when it is invoked by the overpass feature source, so we can't be sure in which layer it should go - const featuresPerLayer = new Map() + const featuresPerLayer = new Map() const noLayerFound = [] for (const layer of layers.data) { @@ -42,7 +43,7 @@ export default class PerLayerFeatureSourceSplitter { for (const f of features) { let foundALayer = false for (const layer of layers.data) { - if (layer.layerDef.source.osmTags.matchesProperties(f.feature.properties)) { + if (layer.layerDef.source.osmTags.matchesProperties(f.properties)) { // We have found our matching layer! featuresPerLayer.get(layer.layerDef.id).push(f) foundALayer = true diff --git a/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts b/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts index 0bac2ab6dc..fb92d69c67 100644 --- a/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts +++ b/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts @@ -6,11 +6,11 @@ import { UIEventSource } from "../../UIEventSource" import { FeatureSourceForLayer, IndexedFeatureSource } from "../FeatureSource" import FilteredLayer from "../../../Models/FilteredLayer" import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription" +import { Feature } from "geojson" export default class ChangeGeometryApplicator implements FeatureSourceForLayer { - public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = - new UIEventSource<{ feature: any; freshness: Date }[]>([]) - public readonly name: string + public readonly features: UIEventSource = + new UIEventSource([]) public readonly layer: FilteredLayer private readonly source: IndexedFeatureSource private readonly changes: Changes @@ -20,8 +20,7 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer { this.changes = changes this.layer = source.layer - this.name = "ChangesApplied(" + source.name + ")" - this.features = new UIEventSource<{ feature: any; freshness: Date }[]>(undefined) + this.features = new UIEventSource(undefined) const self = this source.features.addCallbackAndRunD((_) => self.update()) @@ -58,9 +57,9 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer { changesPerId.set(key, [ch]) } } - const newFeatures: { feature: any; freshness: Date }[] = [] + const newFeatures: Feature[] = [] for (const feature of upstreamFeatures) { - const changesForFeature = changesPerId.get(feature.feature.properties.id) + const changesForFeature = changesPerId.get(feature.properties.id) if (changesForFeature === undefined) { // No changes for this element newFeatures.push(feature) @@ -73,7 +72,7 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer { } // We only apply the last change as that one'll have the latest geometry const change = changesForFeature[changesForFeature.length - 1] - copy.feature.geometry = ChangeDescriptionTools.getGeojsonGeometry(change) + copy.geometry = ChangeDescriptionTools.getGeojsonGeometry(change) console.log( "Applying a geometry change onto:", feature, diff --git a/Logic/FeatureSource/Sources/FeatureSourceMerger.ts b/Logic/FeatureSource/Sources/FeatureSourceMerger.ts index 248a9d9d98..8034ab6975 100644 --- a/Logic/FeatureSource/Sources/FeatureSourceMerger.ts +++ b/Logic/FeatureSource/Sources/FeatureSourceMerger.ts @@ -1,16 +1,13 @@ import { UIEventSource } from "../../UIEventSource" import FeatureSource, { FeatureSourceForLayer, IndexedFeatureSource, Tiled } from "../FeatureSource" import FilteredLayer from "../../../Models/FilteredLayer" -import { Tiles } from "../../../Models/TileRange" import { BBox } from "../../BBox" +import { Feature } from "geojson" export default class FeatureSourceMerger implements FeatureSourceForLayer, Tiled, IndexedFeatureSource { - public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource< - { feature: any; freshness: Date }[] - >([]) - public readonly name + public features: UIEventSource = new UIEventSource([]) public readonly layer: FilteredLayer public readonly tileIndex: number public readonly bbox: BBox @@ -32,12 +29,6 @@ export default class FeatureSourceMerger this.bbox = bbox this._sources = sources this.layer = layer - this.name = - "FeatureSourceMerger(" + - layer.layerDef.id + - ", " + - Tiles.tile_from_index(tileIndex).join(",") + - ")" const self = this const handledSources = new Set() @@ -63,14 +54,11 @@ export default class FeatureSourceMerger private Update() { let somethingChanged = false - const all: Map = new Map< - string, - { feature: any; freshness: Date } - >() + const all: Map = new Map() // We seed the dictionary with the previously loaded features const oldValues = this.features.data ?? [] for (const oldValue of oldValues) { - all.set(oldValue.feature.id, oldValue) + all.set(oldValue.properties.id, oldValue) } for (const source of this._sources.data) { @@ -78,7 +66,7 @@ export default class FeatureSourceMerger continue } for (const f of source.features.data) { - const id = f.feature.properties.id + const id = f.properties.id if (!all.has(id)) { // This is a new feature somethingChanged = true @@ -89,11 +77,11 @@ export default class FeatureSourceMerger // This value has been seen already, either in a previous run or by a previous datasource // Let's figure out if something changed const oldV = all.get(id) - if (oldV.freshness < f.freshness) { - // Jup, this feature is fresher - all.set(id, f) - somethingChanged = true + if (oldV === f) { + continue } + all.set(id, f) + somethingChanged = true } } diff --git a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts index 9bb3546256..2a36553731 100644 --- a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts @@ -4,13 +4,10 @@ import { FeatureSourceForLayer, Tiled } from "../FeatureSource" import { BBox } from "../../BBox" import { ElementStorage } from "../../ElementStorage" import { TagsFilter } from "../../Tags/TagsFilter" -import { OsmFeature } from "../../../Models/OsmFeature" +import { Feature } from "geojson" export default class FilteringFeatureSource implements FeatureSourceForLayer, Tiled { - public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource< - { feature: any; freshness: Date }[] - >([]) - public readonly name + public features: UIEventSource = new UIEventSource([]) public readonly layer: FilteredLayer public readonly tileIndex: number public readonly bbox: BBox @@ -36,7 +33,6 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti upstream: FeatureSourceForLayer, metataggingUpdated?: UIEventSource ) { - this.name = "FilteringFeatureSource(" + upstream.name + ")" this.tileIndex = tileIndex this.bbox = tileIndex === undefined ? undefined : BBox.fromTileIndex(tileIndex) this.upstream = upstream @@ -73,15 +69,14 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti private update() { const self = this const layer = this.upstream.layer - const features: { feature: OsmFeature; freshness: Date }[] = - this.upstream.features.data ?? [] + const features: Feature[] = this.upstream.features.data ?? [] const includedFeatureIds = new Set() const globalFilters = self.state.globalFilters?.data?.map((f) => f.filter) const newFeatures = (features ?? []).filter((f) => { - self.registerCallback(f.feature) + self.registerCallback(f) const isShown: TagsFilter = layer.layerDef.isShown - const tags = f.feature.properties + const tags = f.properties if (isShown !== undefined && !isShown.matchesProperties(tags)) { return false } @@ -92,10 +87,7 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti const tagsFilter = Array.from(layer.appliedFilters?.data?.values() ?? []) for (const filter of tagsFilter) { const neededTags: TagsFilter = filter?.currentFilter - if ( - neededTags !== undefined && - !neededTags.matchesProperties(f.feature.properties) - ) { + if (neededTags !== undefined && !neededTags.matchesProperties(f.properties)) { // Hidden by the filter on the layer itself - we want to hide it no matter what return false } @@ -103,16 +95,13 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti for (const filter of globalFilters ?? []) { const neededTags: TagsFilter = filter?.currentFilter - if ( - neededTags !== undefined && - !neededTags.matchesProperties(f.feature.properties) - ) { + if (neededTags !== undefined && !neededTags.matchesProperties(f.properties)) { // Hidden by the filter on the layer itself - we want to hide it no matter what return false } } - includedFeatureIds.add(f.feature.properties.id) + includedFeatureIds.add(f.properties.id) return true }) diff --git a/Logic/FeatureSource/Sources/GeoJsonSource.ts b/Logic/FeatureSource/Sources/GeoJsonSource.ts index 5c0ecb9e91..99525508bf 100644 --- a/Logic/FeatureSource/Sources/GeoJsonSource.ts +++ b/Logic/FeatureSource/Sources/GeoJsonSource.ts @@ -8,9 +8,10 @@ import { FeatureSourceForLayer, Tiled } from "../FeatureSource" import { Tiles } from "../../../Models/TileRange" import { BBox } from "../../BBox" import { GeoOperations } from "../../GeoOperations" +import { Feature } from "geojson" export default class GeoJsonSource implements FeatureSourceForLayer, Tiled { - public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> + public readonly features: UIEventSource public readonly state = new UIEventSource(undefined) public readonly name public readonly isOsmCache: boolean @@ -69,7 +70,7 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled { this.name = "GeoJsonSource of " + url this.isOsmCache = flayer.layerDef.source.isOsmCacheLayer - this.features = new UIEventSource<{ feature: any; freshness: Date }[]>([]) + this.features = new UIEventSource([]) this.LoadJSONFrom(url) } @@ -110,7 +111,7 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled { } const time = new Date() - const newFeatures: { feature: any; freshness: Date }[] = [] + const newFeatures: Feature[] = [] let i = 0 let skipped = 0 for (const feature of json.features) { @@ -146,7 +147,7 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled { freshness = new Date(props["_last_edit:timestamp"]) } - newFeatures.push({ feature: feature, freshness: freshness }) + newFeatures.push(feature) } if (newFeatures.length == 0) { diff --git a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts index 6829a25a4d..86e92ae8f6 100644 --- a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts +++ b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts @@ -5,6 +5,7 @@ import { UIEventSource } from "../../UIEventSource" import { ChangeDescription } from "../../Osm/Actions/ChangeDescription" import { ElementStorage } from "../../ElementStorage" import { OsmId, OsmTags } from "../../../Models/OsmFeature" +import { Feature } from "geojson" export class NewGeometryFromChangesFeatureSource implements FeatureSource { // This class name truly puts the 'Java' into 'Javascript' @@ -15,9 +16,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { * These elements are probably created by the 'SimpleAddUi' which generates a new point, but the import functionality might create a line or polygon too. * Other sources of new points are e.g. imports from nodes */ - public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = - new UIEventSource<{ feature: any; freshness: Date }[]>([]) - public readonly name: string = "newFeatures" + public readonly features: UIEventSource = new UIEventSource([]) constructor(changes: Changes, allElementStorage: ElementStorage, backendUrl: string) { const seenChanges = new Set() @@ -29,15 +28,11 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { return } - const now = new Date() let somethingChanged = false function add(feature) { feature.id = feature.properties.id - features.push({ - feature: feature, - freshness: now, - }) + features.push(feature) somethingChanged = true } @@ -71,7 +66,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { } const geojson = feat.asGeoJson() allElementStorage.addOrGetElement(geojson) - self.features.data.push({ feature: geojson, freshness: new Date() }) + self.features.data.push(geojson) self.features.ping() }) continue diff --git a/Logic/FeatureSource/Sources/RememberingSource.ts b/Logic/FeatureSource/Sources/RememberingSource.ts index 5d71541c4c..fcc4b7f016 100644 --- a/Logic/FeatureSource/Sources/RememberingSource.ts +++ b/Logic/FeatureSource/Sources/RememberingSource.ts @@ -5,28 +5,25 @@ import FeatureSource, { Tiled } from "../FeatureSource" import { Store, UIEventSource } from "../../UIEventSource" import { BBox } from "../../BBox" +import { Feature } from "geojson" export default class RememberingSource implements FeatureSource, Tiled { - public readonly features: Store<{ feature: any; freshness: Date }[]> - public readonly name + public readonly features: Store public readonly tileIndex: number public readonly bbox: BBox constructor(source: FeatureSource & Tiled) { const self = this - this.name = "RememberingSource of " + source.name this.tileIndex = source.tileIndex this.bbox = source.bbox const empty = [] - const featureSource = new UIEventSource<{ feature: any; freshness: Date }[]>(empty) + const featureSource = new UIEventSource(empty) this.features = featureSource source.features.addCallbackAndRunD((features) => { const oldFeatures = self.features?.data ?? empty // Then new ids - const ids = new Set( - features.map((f) => f.feature.properties.id + f.feature.geometry.type) - ) + const ids = new Set(features.map((f) => f.properties.id + f.geometry.type)) // the old data const oldData = oldFeatures.filter( (old) => !ids.has(old.feature.properties.id + old.feature.geometry.type) diff --git a/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts b/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts index 6a07fb7aaa..0b929713e2 100644 --- a/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts +++ b/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts @@ -1,13 +1,12 @@ -/** - * This feature source helps the ShowDataLayer class: it introduces the necessary extra features and indicates with what renderConfig it should be rendered. - */ import { Store } from "../../UIEventSource" import { GeoOperations } from "../../GeoOperations" import FeatureSource from "../FeatureSource" import PointRenderingConfig from "../../../Models/ThemeConfig/PointRenderingConfig" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import LineRenderingConfig from "../../../Models/ThemeConfig/LineRenderingConfig" - +/** + * This feature source helps the ShowDataLayer class: it introduces the necessary extra features and indicates with what renderConfig it should be rendered. + */ export default class RenderingMultiPlexerFeatureSource { public readonly features: Store< (any & { @@ -64,8 +63,7 @@ export default class RenderingMultiPlexerFeatureSource { withIndex.push(patched) } - for (const f of features) { - const feat = f.feature + for (const feat of features) { if (feat === undefined) { continue } diff --git a/Logic/FeatureSource/Sources/SimpleFeatureSource.ts b/Logic/FeatureSource/Sources/SimpleFeatureSource.ts index f2dfec6788..ca08543374 100644 --- a/Logic/FeatureSource/Sources/SimpleFeatureSource.ts +++ b/Logic/FeatureSource/Sources/SimpleFeatureSource.ts @@ -2,23 +2,18 @@ import { UIEventSource } from "../../UIEventSource" import FilteredLayer from "../../../Models/FilteredLayer" import { FeatureSourceForLayer, Tiled } from "../FeatureSource" import { BBox } from "../../BBox" +import { Feature } from "geojson" export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled { - public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> - public readonly name: string = "SimpleFeatureSource" + public readonly features: UIEventSource public readonly layer: FilteredLayer public readonly bbox: BBox = BBox.global public readonly tileIndex: number - constructor( - layer: FilteredLayer, - tileIndex: number, - featureSource?: UIEventSource<{ feature: any; freshness: Date }[]> - ) { - this.name = "SimpleFeatureSource(" + layer.layerDef.id + ")" + constructor(layer: FilteredLayer, tileIndex: number, featureSource?: UIEventSource) { this.layer = layer this.tileIndex = tileIndex ?? 0 this.bbox = BBox.fromTileIndex(this.tileIndex) - this.features = featureSource ?? new UIEventSource<{ feature: any; freshness: Date }[]>([]) + this.features = featureSource ?? new UIEventSource([]) } } diff --git a/Logic/FeatureSource/Sources/StaticFeatureSource.ts b/Logic/FeatureSource/Sources/StaticFeatureSource.ts index d99304a47f..97d6600cc8 100644 --- a/Logic/FeatureSource/Sources/StaticFeatureSource.ts +++ b/Logic/FeatureSource/Sources/StaticFeatureSource.ts @@ -8,63 +8,34 @@ import { Feature } from "geojson" * A simple, read only feature store. */ export default class StaticFeatureSource implements FeatureSource { - public readonly features: Store<{ feature: any; freshness: Date }[]> - public readonly name: string + public readonly features: Store constructor( - features: Store<{ feature: Feature; freshness: Date }[]>, - name = "StaticFeatureSource" + features: + | Store + | Feature[] + | { features: Feature[] } + | { features: Store } ) { if (features === undefined) { throw "Static feature source received undefined as source" } - this.name = name - this.features = features + let feats: Feature[] | Store + if (features["features"]) { + feats = features["features"] + } else { + feats = >features + } + + if (Array.isArray(feats)) { + this.features = new ImmutableStore(feats) + } else { + this.features = feats + } } - public static fromGeojsonAndDate( - features: { feature: Feature; freshness: Date }[], - name = "StaticFeatureSourceFromGeojsonAndDate" - ): StaticFeatureSource { - return new StaticFeatureSource(new ImmutableStore(features), name) - } - - public static fromGeojson( - geojson: Feature[], - name = "StaticFeatureSourceFromGeojson" - ): StaticFeatureSource { - const now = new Date() - return StaticFeatureSource.fromGeojsonAndDate( - geojson.map((feature) => ({ feature, freshness: now })), - name - ) - } - - public static fromGeojsonStore( - geojson: Store, - name = "StaticFeatureSourceFromGeojson" - ): StaticFeatureSource { - const now = new Date() - const mapped: Store<{ feature: Feature; freshness: Date }[]> = geojson.map((features) => - features.map((feature) => ({ feature, freshness: now })) - ) - return new StaticFeatureSource(mapped, name) - } - - static fromDateless( - featureSource: Store<{ feature: Feature }[]>, - name = "StaticFeatureSourceFromDateless" - ) { - const now = new Date() - return new StaticFeatureSource( - featureSource.map((features) => - features.map((feature) => ({ - feature: feature.feature, - freshness: now, - })) - ), - name - ) + public static fromGeojson(geojson: Feature[]): StaticFeatureSource { + return new StaticFeatureSource(geojson) } } @@ -76,11 +47,7 @@ export class TiledStaticFeatureSource public readonly tileIndex: number public readonly layer: FilteredLayer - constructor( - features: Store<{ feature: any; freshness: Date }[]>, - layer: FilteredLayer, - tileIndex: number = 0 - ) { + constructor(features: Store, layer: FilteredLayer, tileIndex: number = 0) { super(features) this.tileIndex = tileIndex this.layer = layer diff --git a/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts b/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts index f312d0a355..13225ed152 100644 --- a/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts @@ -53,11 +53,9 @@ export default class FullNodeDatabaseSource implements TileHierarchy ({ - feature: osmNode.asGeoJson(), - freshness: now, - })) + const asGeojsonFeatures = Array.from(nodesById.values()).map((osmNode) => + osmNode.asGeoJson() + ) const featureSource = new SimpleFeatureSource(this.layer, tileId) featureSource.features.setData(asGeojsonFeatures) diff --git a/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts b/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts index 849ea4c7d2..d2ada64268 100644 --- a/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts @@ -175,7 +175,7 @@ export default class OsmFeatureSource { new PerLayerFeatureSourceSplitter( this.filteredLayers, this.handleTile, - StaticFeatureSource.fromGeojson(geojson.features), + new StaticFeatureSource(geojson.features), { tileIndex: index, } diff --git a/Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource.ts b/Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource.ts index 9327ec7066..d49bf16d3e 100644 --- a/Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource.ts @@ -4,6 +4,7 @@ import FilteredLayer from "../../../Models/FilteredLayer" import TileHierarchy from "./TileHierarchy" import { Tiles } from "../../../Models/TileRange" import { BBox } from "../../BBox" +import { Feature } from "geojson"; /** * Contains all features in a tiled fashion. @@ -29,7 +30,7 @@ export default class TiledFeatureSource public readonly maxFeatureCount: number public readonly name - public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> + public readonly features: UIEventSource public readonly containedIds: Store> public readonly bbox: BBox @@ -80,7 +81,7 @@ export default class TiledFeatureSource if (features === undefined) { return undefined } - return new Set(features.map((f) => f.feature.properties.id)) + return new Set(features.map((f) => f.properties.id)) }) // We register this tile, but only when there is some data in it @@ -132,7 +133,7 @@ export default class TiledFeatureSource * @param features * @private */ - private addFeatures(features: { feature: any; freshness: Date }[]) { + private addFeatures(features: Feature[]) { if (features === undefined || features.length === 0) { return } @@ -180,7 +181,7 @@ export default class TiledFeatureSource const overlapsboundary = [] for (const feature of features) { - const bbox = BBox.get(feature.feature) + const bbox = BBox.get(feature) // There are a few strategies to deal with features that cross tile boundaries diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 47d698ac02..68fb03a3de 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -2,6 +2,7 @@ import SimpleMetaTaggers, { SimpleMetaTagger } from "./SimpleMetaTagger" import { ExtraFuncParams, ExtraFunctions } from "./ExtraFunctions" import LayerConfig from "../Models/ThemeConfig/LayerConfig" import { ElementStorage } from "./ElementStorage" +import { Feature } from "geojson" /** * Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ... @@ -20,7 +21,7 @@ export default class MetaTagging { * Returns true if at least one feature has changed properties */ public static addMetatags( - features: { feature: any; freshness: Date }[], + features: Feature[], params: ExtraFuncParams, layer: LayerConfig, state?: { allElements?: ElementStorage }, @@ -55,8 +56,7 @@ export default class MetaTagging { for (let i = 0; i < features.length; i++) { const ff = features[i] - const feature = ff.feature - const freshness = ff.freshness + const feature = ff let somethingChanged = false let definedTags = new Set(Object.getOwnPropertyNames(feature.properties)) for (const metatag of metatagsToApply) { @@ -72,19 +72,14 @@ export default class MetaTagging { continue } somethingChanged = true - metatag.applyMetaTagsOnFeature(feature, freshness, layer, state) + metatag.applyMetaTagsOnFeature(feature, layer, state) if (options?.evaluateStrict) { for (const key of metatag.keys) { feature.properties[key] } } } else { - const newValueAdded = metatag.applyMetaTagsOnFeature( - feature, - freshness, - layer, - state - ) + const newValueAdded = metatag.applyMetaTagsOnFeature(feature, layer, state) /* Note that the expression: * `somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness)` * Is WRONG diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index eb8479e7fe..1a0fbb8fde 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -17,12 +17,7 @@ export class SimpleMetaTagger { public readonly doc: string public readonly isLazy: boolean public readonly includesDates: boolean - public readonly applyMetaTagsOnFeature: ( - feature: any, - freshness: Date, - layer: LayerConfig, - state - ) => boolean + public readonly applyMetaTagsOnFeature: (feature: any, layer: LayerConfig, state) => boolean /*** * A function that adds some extra data to a feature @@ -41,7 +36,7 @@ export class SimpleMetaTagger { isLazy?: boolean cleanupRetagger?: boolean }, - f: (feature: any, freshness: Date, layer: LayerConfig, state) => boolean + f: (feature: any, layer: LayerConfig, state) => boolean ) { this.keys = docs.keys this.doc = docs.doc @@ -71,7 +66,7 @@ export class ReferencingWaysMetaTagger extends SimpleMetaTagger { isLazy: true, doc: "_referencing_ways contains - for a node - which ways use this this node as point in their geometry. ", }, - (feature, _, __, state) => { + (feature, _, state) => { if (!ReferencingWaysMetaTagger.enabled) { return false } @@ -116,7 +111,7 @@ export class CountryTagger extends SimpleMetaTagger { doc: "The country code of the property (with latlon2country)", includesDates: false, }, - (feature, _, __, state) => { + (feature, _, state) => { let centerPoint: any = GeoOperations.centerpoint(feature) const lat = centerPoint.geometry.coordinates[1] const lon = centerPoint.geometry.coordinates[0] @@ -236,7 +231,7 @@ export default class SimpleMetaTaggers { keys: ["_layer"], includesDates: false, }, - (feature, freshness, layer) => { + (feature, _, layer) => { if (feature.properties._layer === layer.id) { return false } @@ -322,7 +317,7 @@ export default class SimpleMetaTaggers { doc: "If 'units' is defined in the layoutConfig, then this metatagger will rewrite the specified keys to have the canonical form (e.g. `1meter` will be rewritten to `1m`; `1` will be rewritten to `1m` as well)", keys: ["Theme-defined keys"], }, - (feature, _, __, state) => { + (feature, _, state) => { const units = Utils.NoNull( [].concat(...(state?.layoutToUse?.layers?.map((layer) => layer.units) ?? [])) ) @@ -395,7 +390,7 @@ export default class SimpleMetaTaggers { includesDates: true, isLazy: true, }, - (feature, _, __, state) => { + (feature, _, state) => { if (Utils.runningFromConsole) { // We are running from console, thus probably creating a cache // isOpen is irrelevant @@ -508,17 +503,13 @@ export default class SimpleMetaTaggers { private static currentTime = new SimpleMetaTagger( { - keys: ["_now:date", "_now:datetime", "_loaded:date", "_loaded:_datetime"], + keys: ["_now:date", "_now:datetime"], doc: "Adds the time that the data got loaded - pretty much the time of downloading from overpass. The format is YYYY-MM-DD hh:mm, aka 'sortable' aka ISO-8601-but-not-entirely", includesDates: true, }, - (feature, freshness) => { + (feature) => { const now = new Date() - if (typeof freshness === "string") { - freshness = new Date(freshness) - } - function date(d: Date) { return d.toISOString().slice(0, 10) } @@ -529,8 +520,6 @@ export default class SimpleMetaTaggers { feature.properties["_now:date"] = date(now) feature.properties["_now:datetime"] = datetime(now) - feature.properties["_loaded:date"] = date(freshness) - feature.properties["_loaded:datetime"] = datetime(freshness) return true } ) diff --git a/Logic/State/FeaturePipelineState.ts b/Logic/State/FeaturePipelineState.ts index 8c56a0aacf..60e9fa7881 100644 --- a/Logic/State/FeaturePipelineState.ts +++ b/Logic/State/FeaturePipelineState.ts @@ -57,7 +57,7 @@ export default class FeaturePipelineState extends MapState { function registerSource(source: FeatureSourceForLayer & Tiled) { clusterCounter.addTile(source) const sourceBBox = source.features.map((allFeatures) => - BBox.bboxAroundAll(allFeatures.map((f) => BBox.get(f.feature))) + BBox.bboxAroundAll(allFeatures.map(BBox.get)) ) // Do show features indicates if the respective 'showDataLayer' should be shown. It can be hidden by e.g. clustering @@ -131,7 +131,7 @@ export default class FeaturePipelineState extends MapState { handleRawFeatureSource: registerRaw, }) this.metatagRecalculator = new MetaTagRecalculator(this, this.featurePipeline) - this.metatagRecalculator.registerSource(this.currentView, true) + this.metatagRecalculator.registerSource(this.currentView) sourcesToRegister.forEach((source) => self.metatagRecalculator.registerSource(source)) diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts index 59b1770442..724797585a 100644 --- a/Logic/State/MapState.ts +++ b/Logic/State/MapState.ts @@ -213,38 +213,33 @@ export default class MapState extends UserRelatedState { let i = 0 const self = this - const features: Store<{ feature: any; freshness: Date }[]> = this.currentBounds.map( - (bounds) => { - if (bounds === undefined) { - return [] - } - i++ - const feature = { - freshness: new Date(), - feature: { - type: "Feature", - properties: { - id: "current_view-" + i, - current_view: "yes", - zoom: "" + self.locationControl.data.zoom, - }, - geometry: { - type: "Polygon", - coordinates: [ - [ - [bounds.maxLon, bounds.maxLat], - [bounds.minLon, bounds.maxLat], - [bounds.minLon, bounds.minLat], - [bounds.maxLon, bounds.minLat], - [bounds.maxLon, bounds.maxLat], - ], - ], - }, - }, - } - return [feature] + const features: Store = this.currentBounds.map((bounds) => { + if (bounds === undefined) { + return [] } - ) + i++ + const feature = { + type: "Feature", + properties: { + id: "current_view-" + i, + current_view: "yes", + zoom: "" + self.locationControl.data.zoom, + }, + geometry: { + type: "Polygon", + coordinates: [ + [ + [bounds.maxLon, bounds.maxLat], + [bounds.minLon, bounds.maxLat], + [bounds.minLon, bounds.minLat], + [bounds.maxLon, bounds.minLat], + [bounds.maxLon, bounds.maxLat], + ], + ], + }, + } + return [feature] + }) this.currentView = new TiledStaticFeatureSource(features, currentViewLayer) } diff --git a/Models/OsmFeature.ts b/Models/OsmFeature.ts index aee5f08fb3..d8ff8928ee 100644 --- a/Models/OsmFeature.ts +++ b/Models/OsmFeature.ts @@ -1,4 +1,4 @@ -import { Feature, Geometry } from "@turf/turf" +import { Feature, Geometry } from "geojson" export type RelationId = `relation/${number}` export type WayId = `way/${number}` diff --git a/UI/ImportFlow/CompareToAlreadyExistingNotes.ts b/UI/ImportFlow/CompareToAlreadyExistingNotes.ts index c936c7921f..56cc4932d3 100644 --- a/UI/ImportFlow/CompareToAlreadyExistingNotes.ts +++ b/UI/ImportFlow/CompareToAlreadyExistingNotes.ts @@ -106,7 +106,7 @@ export class CompareToAlreadyExistingNotes state, zoomToFeatures: true, leafletMap: comparisonMap.leafletMap, - features: StaticFeatureSource.fromGeojsonStore( + features: new StaticFeatureSource( partitionedImportPoints.map((p) => p.hasNearby) ), popup: (tags, layer) => new FeatureInfoBox(tags, layer, state), diff --git a/UI/ImportFlow/ConflationChecker.ts b/UI/ImportFlow/ConflationChecker.ts index 1b18e67220..5ec1de4434 100644 --- a/UI/ImportFlow/ConflationChecker.ts +++ b/UI/ImportFlow/ConflationChecker.ts @@ -166,7 +166,7 @@ export default class ConflationChecker [osmLiveData.bounds, zoomLevel.GetValue()] ) - const preview = StaticFeatureSource.fromGeojsonStore(geojsonFeatures) + const preview = new StaticFeatureSource(geojsonFeatures) new ShowDataLayer({ layerToShow: new LayerConfig(currentview), @@ -225,7 +225,7 @@ export default class ConflationChecker }, [nearbyCutoff.GetValue().stabilized(500)] ) - const nearbyFeatures = StaticFeatureSource.fromGeojsonStore(geojsonMapped) + const nearbyFeatures = new StaticFeatureSource(geojsonMapped) const paritionedImport = ImportUtils.partitionFeaturesIfNearby( toImport, geojson, @@ -233,7 +233,7 @@ export default class ConflationChecker ) // Featuresource showing OSM-features which are nearby a toImport-feature - const toImportWithNearby = StaticFeatureSource.fromGeojsonStore( + const toImportWithNearby = new StaticFeatureSource( paritionedImport.map((els) => els?.hasNearby ?? []) ) toImportWithNearby.features.addCallback((nearby) => diff --git a/UI/ImportFlow/MapPreview.ts b/UI/ImportFlow/MapPreview.ts index 9348db65b1..9fb71489de 100644 --- a/UI/ImportFlow/MapPreview.ts +++ b/UI/ImportFlow/MapPreview.ts @@ -8,8 +8,6 @@ import Constants from "../../Models/Constants" import { DropDown } from "../Input/DropDown" import { Utils } from "../../Utils" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import BaseLayer from "../../Models/BaseLayer" -import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" import Loc from "../../Models/Loc" import Minimap from "../Base/Minimap" import Attribution from "../BigComponents/Attribution" @@ -140,9 +138,7 @@ export class MapPreview new ShowDataLayer({ layerToShow, zoomToFeatures: true, - features: StaticFeatureSource.fromDateless( - matching.map((features) => features.map((feature) => ({ feature }))) - ), + features: new StaticFeatureSource(matching), leafletMap: map.leafletMap, popup: (tag) => new PreviewPanel(tag), }) diff --git a/UI/Input/LocationInput.ts b/UI/Input/LocationInput.ts index 56f20f4451..38518392cd 100644 --- a/UI/Input/LocationInput.ts +++ b/UI/Input/LocationInput.ts @@ -53,7 +53,7 @@ export default class LocationInput * Used for rendering * @private */ - private readonly _snapToRaw: Store<{ feature: Feature }[]> + private readonly _snapToRaw: Store private readonly _value: Store private readonly _snappedPoint: Store private readonly _maxSnapDistance: number @@ -112,7 +112,7 @@ export default class LocationInput constructor(options?: { minZoom?: number mapBackground?: UIEventSource - snapTo?: UIEventSource<{ feature: Feature }[]> + snapTo?: UIEventSource renderLayerForSnappedPoint?: LayerConfig maxSnapDistance?: number snappedPointTags?: any @@ -276,7 +276,7 @@ export default class LocationInput if (this._snapToRaw !== undefined) { // Show the lines to snap to new ShowDataMultiLayer({ - features: StaticFeatureSource.fromDateless(this._snapToRaw), + features: new StaticFeatureSource(this._snapToRaw), zoomToFeatures: false, leafletMap: this.map.leafletMap, layers: this._state.filteredLayers, @@ -286,12 +286,12 @@ export default class LocationInput if (loc === undefined) { return [] } - return [{ feature: loc }] + return [loc] }) // The 'matchlayer' is the layer which shows the target location new ShowDataLayer({ - features: StaticFeatureSource.fromDateless(matchPoint), + features: new StaticFeatureSource(matchPoint), zoomToFeatures: false, leafletMap: this.map.leafletMap, layerToShow: this._matching_layer, diff --git a/UI/NewPoint/ConfirmLocationOfPoint.ts b/UI/NewPoint/ConfirmLocationOfPoint.ts index df10191c56..8aed2d01d0 100644 --- a/UI/NewPoint/ConfirmLocationOfPoint.ts +++ b/UI/NewPoint/ConfirmLocationOfPoint.ts @@ -3,7 +3,6 @@ import { OsmConnection } from "../../Logic/Osm/OsmConnection" import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" import BaseUIElement from "../BaseUIElement" import LocationInput from "../Input/LocationInput" -import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" import { BBox } from "../../Logic/BBox" import { TagUtils } from "../../Logic/Tags/TagUtils" import { SubtleButton } from "../Base/SubtleButton" @@ -12,7 +11,6 @@ import Translations from "../i18n/Translations" import Svg from "../../Svg" import Toggle from "../Input/Toggle" import SimpleAddUI, { PresetInfo } from "../BigComponents/SimpleAddUI" -import BaseLayer from "../../Models/BaseLayer" import Img from "../Base/Img" import Title from "../Base/Title" import { GlobalFilter } from "../../Logic/State/MapState" @@ -20,6 +18,8 @@ import { VariableUiElement } from "../Base/VariableUIElement" import { Tag } from "../../Logic/Tags/Tag" import { WayId } from "../../Models/OsmFeature" import { Translation } from "../i18n/Translation" +import { Feature } from "geojson"; +import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers"; export default class ConfirmLocationOfPoint extends Combine { constructor( @@ -28,7 +28,7 @@ export default class ConfirmLocationOfPoint extends Combine { featureSwitchIsTesting: UIEventSource osmConnection: OsmConnection featurePipeline: FeaturePipeline - backgroundLayer?: UIEventSource + backgroundLayer?: UIEventSource }, filterViewIsOpened: UIEventSource, preset: PresetInfo, @@ -55,10 +55,10 @@ export default class ConfirmLocationOfPoint extends Combine { const locationSrc = new UIEventSource(zloc) let backgroundLayer = new UIEventSource( - state?.backgroundLayer?.data ?? AvailableBaseLayers.osmCarto + state?.backgroundLayer?.data ?? AvailableRasterLayers.osmCarto ) if (preset.preciseInput.preferredBackground) { - const defaultBackground = AvailableBaseLayers.SelectBestLayerAccordingTo( + const defaultBackground = AvailableRasterLayers.SelectBestLayerAccordingTo( locationSrc, new UIEventSource(preset.preciseInput.preferredBackground) ) @@ -66,10 +66,10 @@ export default class ConfirmLocationOfPoint extends Combine { backgroundLayer.setData(defaultBackground.data) } - let snapToFeatures: UIEventSource<{ feature: any }[]> = undefined + let snapToFeatures: UIEventSource = undefined let mapBounds: UIEventSource = undefined if (preset.preciseInput.snapToLayers && preset.preciseInput.snapToLayers.length > 0) { - snapToFeatures = new UIEventSource<{ feature: any }[]>([]) + snapToFeatures = new UIEventSource< Feature[]>([]) mapBounds = new UIEventSource(undefined) } @@ -105,13 +105,13 @@ export default class ConfirmLocationOfPoint extends Combine { Math.max(preset.boundsFactor ?? 0.25, 2) ) loadedBbox = bbox - const allFeatures: { feature: any }[] = [] + const allFeatures: Feature[] = [] preset.preciseInput.snapToLayers.forEach((layerId) => { console.log("Snapping to", layerId) state.featurePipeline .GetFeaturesWithin(layerId, bbox) ?.forEach((feats) => - allFeatures.push(...feats.map((f) => ({ feature: f }))) + allFeatures.push(...feats) ) }) console.log("Snapping to", allFeatures) diff --git a/test.ts b/test.ts index 23dde7df0b..167ce125a7 100644 --- a/test.ts +++ b/test.ts @@ -1,20 +1,17 @@ import SvelteUIElement from "./UI/Base/SvelteUIElement" import MaplibreMap from "./UI/Map/MaplibreMap.svelte" -import { ImmutableStore, UIEventSource } from "./Logic/UIEventSource" +import { UIEventSource } from "./Logic/UIEventSource" import { MapLibreAdaptor } from "./UI/Map/MapLibreAdaptor" import { AvailableRasterLayers, RasterLayerPolygon } from "./Models/RasterLayers" import type { Map as MlMap } from "maplibre-gl" -import RasterLayerPicker from "./UI/Map/RasterLayerPicker.svelte" -import BackgroundLayerResetter from "./Logic/Actors/BackgroundLayerResetter" import { ShowDataLayer } from "./UI/Map/ShowDataLayer" -import StaticFeatureSource from "./Logic/FeatureSource/Sources/StaticFeatureSource" -import { Layer } from "leaflet" import LayerConfig from "./Models/ThemeConfig/LayerConfig" import * as bench from "./assets/generated/layers/bench.json" import { Utils } from "./Utils" import SimpleFeatureSource from "./Logic/FeatureSource/Sources/SimpleFeatureSource" import { FilterState } from "./Models/FilteredLayer" import { FixedUiElement } from "./UI/Base/FixedUiElement" + async function main() { const mlmap = new UIEventSource(undefined) const location = new UIEventSource<{ lon: number; lat: number }>({ From 4d48b1cf2ba7c0355d16b9f9a85f1a2fd4e0c75b Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 24 Mar 2023 19:21:15 +0100 Subject: [PATCH 004/257] refactoring(maplibre): WIP --- Customizations/AllKnownLayouts.ts | 10 + Logic/Actors/GeoLocationHandler.ts | 164 +++- Logic/Actors/InitialMapPositioning.ts | 64 ++ Logic/BBox.ts | 8 - Logic/ExtraFunctions.ts | 4 +- .../RenderingMultiPlexerFeatureSource.ts | 168 ---- Logic/GeoOperations.ts | 73 +- Logic/Maproulette.ts | 3 +- .../CreateMultiPolygonWithPointReuseAction.ts | 9 - Logic/Osm/Changes.ts | 13 +- Logic/Osm/Geocoding.ts | 5 +- Logic/State/ElementsState.ts | 91 --- Logic/State/FeaturePipelineState.ts | 36 +- Logic/State/GeoLocationState.ts | 4 +- Logic/State/MapState.ts | 248 +----- Logic/State/UserRelatedState.ts | 153 ++-- Models/GlobalFilter.ts | 13 + Models/LeafletMap.ts | 3 - Models/MapProperties.ts | 14 + Models/ThemeConfig/Conversion/PrepareLayer.ts | 2 - Models/ThemeConfig/Conversion/PrepareTheme.ts | 4 +- Models/ThemeConfig/Conversion/Validation.ts | 47 ++ Models/ThemeConfig/FilterConfig.ts | 7 +- Models/ThemeConfig/Json/LayerConfigJson.ts | 5 +- .../Json/PointRenderingConfigJson.ts | 7 +- Models/ThemeConfig/LayerConfig.ts | 19 +- Models/ThemeConfig/LayoutConfig.ts | 1 - Models/ThemeConfig/PointRenderingConfig.ts | 4 +- Models/ThemeConfig/TagRenderingConfig.ts | 20 +- UI/AutomatonGui.ts | 476 ----------- UI/Base/If.svelte | 14 + UI/Base/MapControlButton.svelte | 13 + UI/Base/Minimap.ts | 47 -- UI/Base/MinimapImplementation.ts | 422 ---------- UI/Base/SvelteUIElement.ts | 1 + UI/BigComponents/AddNewMarker.ts | 6 +- UI/BigComponents/Attribution.ts | 92 --- UI/BigComponents/GeolocationControl.ts | 30 +- UI/BigComponents/LeftControls.ts | 3 +- UI/BigComponents/LevelSelector.ts | 3 +- UI/BigComponents/RightControls.ts | 25 +- UI/BigComponents/SearchAndGo.ts | 11 +- UI/BigComponents/SimpleAddUI.ts | 7 +- UI/DashboardGui.ts | 305 ------- UI/DefaultGUI.ts | 3 - UI/ImportFlow/ConflationChecker.ts | 6 - UI/ImportFlow/MapPreview.ts | 38 +- UI/Map/MapLibreAdaptor.ts | 142 +++- UI/Map/MaplibreMap.svelte | 3 - UI/Map/ShowDataLayer.ts | 268 ++++++- .../ShowDataLayerOptions.ts | 2 +- .../ShowDataMultiLayer.ts | 11 +- UI/NewPoint/ConfirmLocationOfPoint.ts | 12 +- UI/{ => Popup}/AllTagsPanel.svelte | 6 +- UI/Popup/ImportButton.ts | 3 +- UI/Popup/SidedMinimap.ts | 52 -- UI/ShowDataLayer/ShowDataLayer.ts | 27 - .../ShowDataLayerImplementation.ts | 407 ---------- UI/ShowDataLayer/ShowTileInfo.ts | 64 -- UI/ShowDataLayer/TileHierarchyAggregator.ts | 57 +- UI/SpecialVisualizations.ts | 6 +- UI/ThemeViewGUI.svelte | 114 +++ UI/i18n/Translation.ts | 3 +- all_themes_index.ts | 3 - .../layers/cluster_style/cluster_style.json | 49 -- assets/layers/conflation/conflation.json | 11 +- assets/layers/current_view/current_view.json | 7 +- assets/layers/filters/filters.json | 4 +- assets/layers/gps_location/gps_location.json | 7 +- .../gps_location_history.json | 8 +- assets/layers/gps_track/gps_track.json | 5 +- .../layers/home_location/home_location.json | 7 +- assets/layers/icons/icons.json | 6 +- assets/layers/id_presets/id_presets.json | 6 +- .../import_candidate/import_candidate.json | 8 +- .../left_right_style/left_right_style.json | 35 - assets/layers/matchpoint/matchpoint.json | 8 +- assets/layers/range/range.json | 14 + assets/layers/type_node/type_node.json | 7 +- assets/layers/usersettings/usersettings.json | 6 +- css/index-tailwind-output.css | 198 +++-- index.ts | 16 +- package.json | 6 - public/vendor/images/layers-2x.png | Bin 1259 -> 0 bytes public/vendor/images/layers.png | Bin 696 -> 0 bytes public/vendor/images/marker-icon.png | Bin 1466 -> 0 bytes public/vendor/leaflet.css | 754 ------------------ test.ts | 95 +-- theme.html | 1 - 89 files changed, 1166 insertions(+), 3973 deletions(-) create mode 100644 Logic/Actors/InitialMapPositioning.ts delete mode 100644 Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts delete mode 100644 Logic/State/ElementsState.ts create mode 100644 Models/GlobalFilter.ts delete mode 100644 Models/LeafletMap.ts create mode 100644 Models/MapProperties.ts delete mode 100644 UI/AutomatonGui.ts create mode 100644 UI/Base/If.svelte create mode 100644 UI/Base/MapControlButton.svelte delete mode 100644 UI/Base/Minimap.ts delete mode 100644 UI/Base/MinimapImplementation.ts delete mode 100644 UI/BigComponents/Attribution.ts delete mode 100644 UI/DashboardGui.ts rename UI/{ShowDataLayer => Map}/ShowDataLayerOptions.ts (95%) rename UI/{ShowDataLayer => Map}/ShowDataMultiLayer.ts (73%) rename UI/{ => Popup}/AllTagsPanel.svelte (88%) delete mode 100644 UI/Popup/SidedMinimap.ts delete mode 100644 UI/ShowDataLayer/ShowDataLayer.ts delete mode 100644 UI/ShowDataLayer/ShowDataLayerImplementation.ts delete mode 100644 UI/ShowDataLayer/ShowTileInfo.ts create mode 100644 UI/ThemeViewGUI.svelte delete mode 100644 assets/layers/cluster_style/cluster_style.json delete mode 100644 assets/layers/left_right_style/left_right_style.json create mode 100644 assets/layers/range/range.json delete mode 100644 public/vendor/images/layers-2x.png delete mode 100644 public/vendor/images/layers.png delete mode 100644 public/vendor/images/marker-icon.png delete mode 100644 public/vendor/leaflet.css diff --git a/Customizations/AllKnownLayouts.ts b/Customizations/AllKnownLayouts.ts index 12c27223c5..f01bfe24d7 100644 --- a/Customizations/AllKnownLayouts.ts +++ b/Customizations/AllKnownLayouts.ts @@ -49,4 +49,14 @@ export class AllKnownLayoutsLazy { export class AllKnownLayouts { public static allKnownLayouts: AllKnownLayoutsLazy = new AllKnownLayoutsLazy() + + static AllPublicLayers() { + const layers = [].concat( + ...this.allKnownLayouts + .values() + .filter((layout) => !layout.hideFromOverview) + .map((layout) => layout.layers) + ) + return layers + } } diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index a5f51959e1..fe709f3e11 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -3,9 +3,13 @@ import { BBox } from "../BBox" import Constants from "../../Models/Constants" import { GeoLocationPointProperties, GeoLocationState } from "../State/GeoLocationState" import { UIEventSource } from "../UIEventSource" -import Loc from "../../Models/Loc" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource" +import { Feature, LineString, Point } from "geojson" +import FeatureSource from "../FeatureSource/FeatureSource" +import { LocalStorageSource } from "../Web/LocalStorageSource" +import { GeoOperations } from "../GeoOperations" +import { OsmTags } from "../../Models/OsmFeature" +import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" +import { MapProperties } from "../../Models/MapProperties" /** * The geolocation-handler takes a map-location and a geolocation state. @@ -14,28 +18,39 @@ import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource" */ export default class GeoLocationHandler { public readonly geolocationState: GeoLocationState - private readonly _state: { - currentUserLocation: SimpleFeatureSource - layoutToUse: LayoutConfig - locationControl: UIEventSource - selectedElement: UIEventSource - leafletMap?: UIEventSource - } + + /** + * The location as delivered by the GPS, wrapped as FeatureSource + */ + public currentUserLocation: FeatureSource + + /** + * All previously visited points (as 'Point'-objects), with their metadata + */ + public historicalUserLocations: FeatureSource + + /** + * A featureSource containing a single linestring which has the GPS-history of the user. + * However, metadata (such as when every single point was visited) is lost here (but is kept in `historicalUserLocations`. + * Note that this featureSource is _derived_ from 'historicalUserLocations' + */ + public historicalUserLocationsTrack: FeatureSource public readonly mapHasMoved: UIEventSource = new UIEventSource(false) + private readonly selectedElement: UIEventSource + private readonly mapProperties?: MapProperties + private readonly gpsLocationHistoryRetentionTime?: UIEventSource constructor( geolocationState: GeoLocationState, - state: { - locationControl: UIEventSource - currentUserLocation: SimpleFeatureSource - layoutToUse: LayoutConfig - selectedElement: UIEventSource - leafletMap?: UIEventSource - } + selectedElement: UIEventSource, + mapProperties?: MapProperties, + gpsLocationHistoryRetentionTime?: UIEventSource ) { this.geolocationState = geolocationState - this._state = state - const mapLocation = state.locationControl + const mapLocation = mapProperties.location + this.selectedElement = selectedElement + this.mapProperties = mapProperties + this.gpsLocationHistoryRetentionTime = gpsLocationHistoryRetentionTime // Did an interaction move the map? let self = this let initTime = new Date() @@ -54,7 +69,7 @@ export default class GeoLocationHandler { this.mapHasMoved.setData(true) } - this.geolocationState.currentGPSLocation.addCallbackAndRunD((newLocation) => { + this.geolocationState.currentGPSLocation.addCallbackAndRunD((_) => { const timeSinceLastRequest = (new Date().getTime() - geolocationState.requestMoment.data?.getTime() ?? 0) / 1000 if (!this.mapHasMoved.data) { @@ -65,25 +80,17 @@ export default class GeoLocationHandler { self.MoveMapToCurrentLocation() } - if (this.geolocationState.isLocked.data) { + if (!this.geolocationState.allowMoving.data) { // Jup, the map is locked to the bound location: move automatically self.MoveMapToCurrentLocation() return } }) - geolocationState.isLocked.map( - (isLocked) => { - if (isLocked) { - state.leafletMap?.data?.dragging?.disable() - } else { - state.leafletMap?.data?.dragging?.enable() - } - }, - [state.leafletMap] - ) + geolocationState.allowMoving.syncWith(mapProperties.allowMoving, true) this.CopyGeolocationIntoMapstate() + this.initUserLocationTrail() } /** @@ -95,12 +102,11 @@ export default class GeoLocationHandler { */ public MoveMapToCurrentLocation() { const newLocation = this.geolocationState.currentGPSLocation.data - const mapLocation = this._state.locationControl - const state = this._state + const mapLocation = this.mapProperties.location // We got a new location. // Do we move the map to it? - if (state.selectedElement.data !== undefined) { + if (this.selectedElement.data !== undefined) { // Nope, there is something selected, so we don't move to the current GPS-location return } @@ -110,8 +116,8 @@ export default class GeoLocationHandler { } // We check that the GPS location is not out of bounds - const bounds = state.layoutToUse.lockLocation - if (bounds && bounds !== true) { + const bounds = this.mapProperties.maxbounds.data + if (bounds !== undefined) { // B is an array with our lock-location const inRange = new BBox(bounds).contains([newLocation.longitude, newLocation.latitude]) if (!inRange) { @@ -119,22 +125,25 @@ export default class GeoLocationHandler { } } + console.trace("Moving the map to the GPS-location") mapLocation.setData({ - zoom: Math.max(mapLocation.data.zoom, 16), lon: newLocation.longitude, lat: newLocation.latitude, }) + const zoom = this.mapProperties.zoom + zoom.setData(Math.max(zoom.data, 16)) this.mapHasMoved.setData(true) this.geolocationState.requestMoment.setData(undefined) } private CopyGeolocationIntoMapstate() { - const state = this._state + const features: UIEventSource = new UIEventSource([]) + this.currentUserLocation = new StaticFeatureSource(features) this.geolocationState.currentGPSLocation.addCallbackAndRun((location) => { if (location === undefined) { return } - const feature = { + const feature = { type: "Feature", properties: { id: "gps", @@ -148,7 +157,82 @@ export default class GeoLocationHandler { }, } - state.currentUserLocation?.features?.setData([{ feature, freshness: new Date() }]) + features.setData([feature]) }) } + + private initUserLocationTrail() { + const features = LocalStorageSource.GetParsed("gps_location_history", []) + const now = new Date().getTime() + features.data = features.data.filter((ff) => { + if (ff.properties === undefined) { + return false + } + const point_time = new Date(ff.properties["date"]) + return ( + now - point_time.getTime() < + 1000 * (this.gpsLocationHistoryRetentionTime?.data ?? 24 * 60 * 60 * 1000) + ) + }) + features.ping() + let i = 0 + this.currentUserLocation?.features?.addCallbackAndRunD(([location]: [Feature]) => { + if (location === undefined) { + return + } + + const previousLocation = >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 + i++ + features.data.push(feature) + features.ping() + }) + + this.historicalUserLocations = new StaticFeatureSource(features) + + const asLine = features.map((allPoints) => { + if (allPoints === undefined || allPoints.length < 2) { + return [] + } + + const feature: Feature = { + type: "Feature", + properties: { + id: "location_track", + "_date:now": new Date().toISOString(), + }, + geometry: { + type: "LineString", + coordinates: allPoints.map( + (ff: Feature) => <[number, number]>ff.geometry.coordinates + ), + }, + } + return [feature] + }) + this.historicalUserLocationsTrack = new StaticFeatureSource(asLine) + } } diff --git a/Logic/Actors/InitialMapPositioning.ts b/Logic/Actors/InitialMapPositioning.ts new file mode 100644 index 0000000000..a51aea6c21 --- /dev/null +++ b/Logic/Actors/InitialMapPositioning.ts @@ -0,0 +1,64 @@ +import { UIEventSource } from "../UIEventSource" +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" +import { LocalStorageSource } from "../Web/LocalStorageSource" +import { QueryParameters } from "../Web/QueryParameters" + +/** + * This actor is responsible to set the map location. + * It will attempt to + * - Set the map to the position as passed in by the query parameters (if available) + * - Set the map to the position remembered in LocalStorage (if available) + * - Set the map to the layout default + * + * Additionally, it will save the map location to local storage + */ +export default class InitialMapPositioning { + public zoom: UIEventSource + public location: UIEventSource<{ lon: number; lat: number }> + constructor(layoutToUse: LayoutConfig) { + function localStorageSynced( + key: string, + deflt: number, + docs: string + ): UIEventSource { + const localStorage = LocalStorageSource.Get(key) + const previousValue = localStorage.data + const src = UIEventSource.asFloat( + QueryParameters.GetQueryParameter(key, "" + deflt, docs).syncWith(localStorage) + ) + + if (src.data === deflt) { + const prev = Number(previousValue) + if (!isNaN(prev)) { + src.setData(prev) + } + } + + return src + } + + // -- Location control initialization + this.zoom = localStorageSynced( + "z", + layoutToUse?.startZoom ?? 1, + "The initial/current zoom level" + ) + const lat = localStorageSynced( + "lat", + layoutToUse?.startLat ?? 0, + "The initial/current latitude" + ) + const lon = localStorageSynced( + "lon", + layoutToUse?.startLon ?? 0, + "The initial/current longitude of the app" + ) + + this.location = new UIEventSource({ lon: lon.data, lat: lat.data }) + this.location.addCallbackD((loc) => { + lat.setData(loc.lat) + lon.setData(loc.lon) + }) + // Note: this syncs only in one direction + } +} diff --git a/Logic/BBox.ts b/Logic/BBox.ts index 8e8bc14c6b..c23f2073ca 100644 --- a/Logic/BBox.ts +++ b/Logic/BBox.ts @@ -179,13 +179,6 @@ export class BBox { ]) } - toLeaflet(): [[number, number], [number, number]] { - return [ - [this.minLat, this.minLon], - [this.maxLat, this.maxLon], - ] - } - toLngLat(): [[number, number], [number, number]] { return [ [this.minLon, this.minLat], @@ -193,7 +186,6 @@ export class BBox { ] } - public asGeoJson(properties: T): Feature { return { type: "Feature", diff --git a/Logic/ExtraFunctions.ts b/Logic/ExtraFunctions.ts index dd568cab42..3da6f34b4f 100644 --- a/Logic/ExtraFunctions.ts +++ b/Logic/ExtraFunctions.ts @@ -5,7 +5,7 @@ import BaseUIElement from "../UI/BaseUIElement" import List from "../UI/Base/List" import Title from "../UI/Base/Title" import { BBox } from "./BBox" -import { Feature, Geometry, MultiPolygon, Polygon } from "@turf/turf" +import { Feature, Geometry, MultiPolygon, Polygon } from "geojson" export interface ExtraFuncParams { /** @@ -68,7 +68,7 @@ class EnclosingFunc implements ExtraFunction { } if ( GeoOperations.completelyWithin( - feat, + feat, >otherFeature ) ) { diff --git a/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts b/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts deleted file mode 100644 index 0b929713e2..0000000000 --- a/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { Store } from "../../UIEventSource" -import { GeoOperations } from "../../GeoOperations" -import FeatureSource from "../FeatureSource" -import PointRenderingConfig from "../../../Models/ThemeConfig/PointRenderingConfig" -import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" -import LineRenderingConfig from "../../../Models/ThemeConfig/LineRenderingConfig" -/** - * This feature source helps the ShowDataLayer class: it introduces the necessary extra features and indicates with what renderConfig it should be rendered. - */ -export default class RenderingMultiPlexerFeatureSource { - public readonly features: Store< - (any & { - pointRenderingIndex: number | undefined - lineRenderingIndex: number | undefined - })[] - > - private readonly pointRenderings: { rendering: PointRenderingConfig; index: number }[] - private readonly centroidRenderings: { rendering: PointRenderingConfig; index: number }[] - private readonly projectedCentroidRenderings: { - rendering: PointRenderingConfig - index: number - }[] - private readonly startRenderings: { rendering: PointRenderingConfig; index: number }[] - private readonly endRenderings: { rendering: PointRenderingConfig; index: number }[] - private readonly hasCentroid: boolean - private lineRenderObjects: LineRenderingConfig[] - - constructor(upstream: FeatureSource, layer: LayerConfig) { - const pointRenderObjects: { rendering: PointRenderingConfig; index: number }[] = - layer.mapRendering.map((r, i) => ({ - rendering: r, - index: i, - })) - this.pointRenderings = pointRenderObjects.filter((r) => r.rendering.location.has("point")) - this.centroidRenderings = pointRenderObjects.filter((r) => - r.rendering.location.has("centroid") - ) - this.projectedCentroidRenderings = pointRenderObjects.filter((r) => - r.rendering.location.has("projected_centerpoint") - ) - this.startRenderings = pointRenderObjects.filter((r) => r.rendering.location.has("start")) - this.endRenderings = pointRenderObjects.filter((r) => r.rendering.location.has("end")) - this.hasCentroid = - this.centroidRenderings.length > 0 || this.projectedCentroidRenderings.length > 0 - this.lineRenderObjects = layer.lineRendering - - this.features = upstream.features.map((features) => { - if (features === undefined) { - return undefined - } - - const withIndex: any[] = [] - - function addAsPoint(feat, rendering, coordinate) { - const patched = { - ...feat, - pointRenderingIndex: rendering.index, - } - patched.geometry = { - type: "Point", - coordinates: coordinate, - } - withIndex.push(patched) - } - - for (const feat of features) { - if (feat === undefined) { - continue - } - this.inspectFeature(feat, addAsPoint, withIndex) - } - - return withIndex - }) - } - - /** - * For every source feature, adds the necessary rendering-features - */ - private inspectFeature( - feat, - addAsPoint: (feat, rendering, centerpoint: [number, number]) => void, - withIndex: any[] - ) { - if (feat.geometry.type === "Point") { - for (const rendering of this.pointRenderings) { - withIndex.push({ - ...feat, - pointRenderingIndex: rendering.index, - }) - } - } else if (feat.geometry.type === "MultiPolygon") { - if (this.centroidRenderings.length > 0 || this.projectedCentroidRenderings.length > 0) { - const centerpoints: [number, number][] = (<[number, number][][][]>( - feat.geometry.coordinates - )).map((rings) => - GeoOperations.centerpointCoordinates({ - type: "Feature", - properties: {}, - geometry: { type: "Polygon", coordinates: rings }, - }) - ) - for (const centroidRendering of this.centroidRenderings) { - for (const centerpoint of centerpoints) { - addAsPoint(feat, centroidRendering, centerpoint) - } - } - - for (const centroidRendering of this.projectedCentroidRenderings) { - for (const centerpoint of centerpoints) { - addAsPoint(feat, centroidRendering, centerpoint) - } - } - } - - // AT last, add it 'as is' to what we should render - for (let i = 0; i < this.lineRenderObjects.length; i++) { - withIndex.push({ - ...feat, - lineRenderingIndex: i, - }) - } - } else { - // This is a a line or polygon: add the centroids - let centerpoint: [number, number] = undefined - let projectedCenterPoint: [number, number] = undefined - if (this.hasCentroid) { - centerpoint = GeoOperations.centerpointCoordinates(feat) - if (this.projectedCentroidRenderings.length > 0) { - projectedCenterPoint = <[number, number]>( - GeoOperations.nearestPoint(feat, centerpoint).geometry.coordinates - ) - } - } - for (const rendering of this.centroidRenderings) { - addAsPoint(feat, rendering, centerpoint) - } - - if (feat.geometry.type === "LineString") { - for (const rendering of this.projectedCentroidRenderings) { - addAsPoint(feat, rendering, projectedCenterPoint) - } - - // Add start- and endpoints - const coordinates = feat.geometry.coordinates - for (const rendering of this.startRenderings) { - addAsPoint(feat, rendering, coordinates[0]) - } - for (const rendering of this.endRenderings) { - const coordinate = coordinates[coordinates.length - 1] - addAsPoint(feat, rendering, coordinate) - } - } else { - for (const rendering of this.projectedCentroidRenderings) { - addAsPoint(feat, rendering, centerpoint) - } - } - - // AT last, add it 'as is' to what we should render - for (let i = 0; i < this.lineRenderObjects.length; i++) { - withIndex.push({ - ...feat, - lineRenderingIndex: i, - }) - } - } - } -} diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 2ceb9b2d47..7957fb8cd4 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -1,15 +1,8 @@ import { BBox } from "./BBox" import LayerConfig from "../Models/ThemeConfig/LayerConfig" import * as turf from "@turf/turf" -import { - AllGeoJSON, - booleanWithin, - Coord, - Feature, - Geometry, - MultiPolygon, - Polygon, -} from "@turf/turf" +import { AllGeoJSON, booleanWithin, Coord } from "@turf/turf" +import { Feature, Geometry, MultiPolygon, Polygon } from "geojson" import { GeoJSON, LineString, Point, Position } from "geojson" import togpx from "togpx" import Constants from "../Models/Constants" @@ -263,7 +256,10 @@ export class GeoOperations { * @param way The road on which you want to find a point * @param point Point defined as [lon, lat] */ - public static nearestPoint(way: Feature, point: [number, number]) { + public static nearestPoint( + way: Feature, + point: [number, number] + ): Feature { if (way.geometry.type === "Polygon") { way = { ...way } way.geometry = { ...way.geometry } @@ -710,6 +706,63 @@ export class GeoOperations { return true } + /** + * + * + * const f = (type, feature: Feature) => GeoOperations.featureToCoordinateWithRenderingType(feature, type) + * const g = geometry => ( {type: "Feature", properties: {}, geometry}) + * f("point", g({type:"Point", coordinates:[1,2]})) // => [1,2] + * f("centroid", g({type:"Point", coordinates:[1,2]})) // => undefined + * f("start", g({type:"Point", coordinates:[1,2]})) // => undefined + * f("centroid", g({type:"LineString", coordinates:[[1,2], [3,4]]})) // => [2,3] + * f("centroid", g({type:"Polygon", coordinates:[[[1,2], [3,4], [1,2]]]})) // => [2,3] + * f("projected_centerpoint", g({type:"LineString", coordinates:[[1,2], [3,4]]})) // => [1.9993137596003214,2.999313759600321] + * f("start", g({type:"LineString", coordinates:[[1,2], [3,4]]})) // => [1,2] + * f("end", g({type:"LineString", coordinates:[[1,2], [3,4]]})) // => [3,4] + * + */ + public static featureToCoordinateWithRenderingType( + feature: Feature, + location: "point" | "centroid" | "start" | "end" | "projected_centerpoint" | string + ): [number, number] | undefined { + switch (location) { + case "point": + if (feature.geometry.type === "Point") { + return <[number, number]>feature.geometry.coordinates + } + return undefined + case "centroid": + if (feature.geometry.type === "Point") { + return undefined + } + return GeoOperations.centerpointCoordinates(feature) + case "projected_centerpoint": + if ( + feature.geometry.type === "LineString" || + feature.geometry.type === "MultiLineString" + ) { + const centerpoint = GeoOperations.centerpointCoordinates(feature) + const projected = GeoOperations.nearestPoint( + >feature, + centerpoint + ) + return <[number, number]>projected.geometry.coordinates + } + return undefined + case "start": + if (feature.geometry.type === "LineString") { + return <[number, number]>feature.geometry.coordinates[0] + } + return undefined + case "end": + if (feature.geometry.type === "LineString") { + return <[number, number]>feature.geometry.coordinates.at(-1) + } + return undefined + default: + throw "Unkown location type: " + location + } + } private static pointWithinRing(x: number, y: number, ring: [number, number][]) { let inside = false for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) { diff --git a/Logic/Maproulette.ts b/Logic/Maproulette.ts index 15e11ccbaf..03e3777360 100644 --- a/Logic/Maproulette.ts +++ b/Logic/Maproulette.ts @@ -29,8 +29,9 @@ export default class Maproulette { /** * The API key to use for all requests */ - private apiKey: string + private readonly apiKey: string + public static singleton = new Maproulette() /** * Creates a new Maproulette instance * @param endpoint The API endpoint to use diff --git a/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts b/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts index e7e625fd2e..9e46a3791a 100644 --- a/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts +++ b/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts @@ -59,15 +59,6 @@ export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAct } } - public async getPreview(): Promise { - const outerPreview = await this.createOuterWay.getPreview() - outerPreview.features.data.push({ - freshness: new Date(), - feature: this.geojsonPreview, - }) - return outerPreview - } - protected async CreateChangeDescriptions(changes: Changes): Promise { console.log("Running CMPWPRA") const descriptions: ChangeDescription[] = [] diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index c2568def4b..67e44a4a83 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -22,24 +22,25 @@ export class Changes { /** * All the newly created features as featureSource + all the modified features */ - public features = new UIEventSource<{ feature: any; freshness: Date }[]>([]) + public readonly features = new UIEventSource<{ feature: any; freshness: Date }[]>([]) public readonly pendingChanges: UIEventSource = LocalStorageSource.GetParsed("pending-changes", []) public readonly allChanges = new UIEventSource(undefined) public readonly state: { allElements: ElementStorage; osmConnection: OsmConnection } public readonly extraComment: UIEventSource = new UIEventSource(undefined) - private historicalUserLocations: FeatureSource + private readonly historicalUserLocations: FeatureSource private _nextId: number = -1 // Newly assigned ID's are negative private readonly isUploading = new UIEventSource(false) private readonly previouslyCreated: OsmObject[] = [] private readonly _leftRightSensitive: boolean - private _changesetHandler: ChangesetHandler + private readonly _changesetHandler: ChangesetHandler constructor( state?: { allElements: ElementStorage osmConnection: OsmConnection + historicalUserLocations: FeatureSource }, leftRightSensitive: boolean = false ) { @@ -53,6 +54,7 @@ export class Changes { state.allElements, this ) + this.historicalUserLocations = state.historicalUserLocations // Note: a changeset might be reused which was opened just before and might have already used some ids // This doesn't matter however, as the '-1' is per piecewise upload, not global per changeset @@ -164,7 +166,6 @@ export class Changes { const now = new Date() const recentLocationPoints = locations - .map((ff) => ff.feature) .filter((feat) => feat.geometry.type === "Point") .filter((feat) => { const visitTime = new Date( @@ -582,8 +583,4 @@ export class Changes { ) return result } - - public setHistoricalUserLocations(locations: FeatureSource) { - this.historicalUserLocations = locations - } } diff --git a/Logic/Osm/Geocoding.ts b/Logic/Osm/Geocoding.ts index 4e349d8ba2..d0241608cb 100644 --- a/Logic/Osm/Geocoding.ts +++ b/Logic/Osm/Geocoding.ts @@ -1,4 +1,3 @@ -import State from "../../State" import { Utils } from "../../Utils" import { BBox } from "../BBox" @@ -14,8 +13,8 @@ export interface GeoCodeResult { export class Geocoding { private static readonly host = "https://nominatim.openstreetmap.org/search?" - static async Search(query: string): Promise { - const b = State?.state?.currentBounds?.data ?? BBox.global + static async Search(query: string, bbox: BBox): Promise { + const b = bbox ?? BBox.global const url = Geocoding.host + "format=json&limit=1&viewbox=" + diff --git a/Logic/State/ElementsState.ts b/Logic/State/ElementsState.ts deleted file mode 100644 index cecb04009f..0000000000 --- a/Logic/State/ElementsState.ts +++ /dev/null @@ -1,91 +0,0 @@ -import FeatureSwitchState from "./FeatureSwitchState" -import { ElementStorage } from "../ElementStorage" -import { Changes } from "../Osm/Changes" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import { UIEventSource } from "../UIEventSource" -import Loc from "../../Models/Loc" -import { BBox } from "../BBox" -import { QueryParameters } from "../Web/QueryParameters" -import { LocalStorageSource } from "../Web/LocalStorageSource" -import { Utils } from "../../Utils" -import ChangeToElementsActor from "../Actors/ChangeToElementsActor" -import PendingChangesUploader from "../Actors/PendingChangesUploader" - -/** - * The part of the state keeping track of where the elements, loading them, configuring the feature pipeline etc - */ -export default class ElementsState extends FeatureSwitchState { - /** - The mapping from id -> UIEventSource - */ - public allElements: ElementStorage = new ElementStorage() - - /** - The latest element that was selected - */ - public readonly selectedElement = new UIEventSource(undefined, "Selected element") - - /** - * The map location: currently centered lat, lon and zoom - */ - public readonly locationControl = new UIEventSource(undefined, "locationControl") - - /** - * The current visible extent of the screen - */ - public readonly currentBounds = new UIEventSource(undefined) - - constructor(layoutToUse: LayoutConfig) { - super(layoutToUse) - - function localStorageSynced( - key: string, - deflt: number, - docs: string - ): UIEventSource { - const localStorage = LocalStorageSource.Get(key) - const previousValue = localStorage.data - const src = UIEventSource.asFloat( - QueryParameters.GetQueryParameter(key, "" + deflt, docs).syncWith(localStorage) - ) - - if (src.data === deflt) { - const prev = Number(previousValue) - if (!isNaN(prev)) { - src.setData(prev) - } - } - - return src - } - - // -- Location control initialization - const zoom = localStorageSynced( - "z", - layoutToUse?.startZoom ?? 1, - "The initial/current zoom level" - ) - const lat = localStorageSynced( - "lat", - layoutToUse?.startLat ?? 0, - "The initial/current latitude" - ) - const lon = localStorageSynced( - "lon", - layoutToUse?.startLon ?? 0, - "The initial/current longitude of the app" - ) - - this.locationControl.setData({ - zoom: Utils.asFloat(zoom.data), - lat: Utils.asFloat(lat.data), - lon: Utils.asFloat(lon.data), - }) - this.locationControl.addCallback((latlonz) => { - // Sync the location controls - zoom.setData(latlonz.zoom) - lat.setData(latlonz.lat) - lon.setData(latlonz.lon) - }) - } -} diff --git a/Logic/State/FeaturePipelineState.ts b/Logic/State/FeaturePipelineState.ts index 60e9fa7881..5f7fe819d8 100644 --- a/Logic/State/FeaturePipelineState.ts +++ b/Logic/State/FeaturePipelineState.ts @@ -1,9 +1,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import FeaturePipeline from "../FeatureSource/FeaturePipeline" import { Tiles } from "../../Models/TileRange" -import ShowDataLayer from "../../UI/ShowDataLayer/ShowDataLayer" import { TileHierarchyAggregator } from "../../UI/ShowDataLayer/TileHierarchyAggregator" -import ShowTileInfo from "../../UI/ShowDataLayer/ShowTileInfo" import { UIEventSource } from "../UIEventSource" import MapState from "./MapState" import SelectedFeatureHandler from "../Actors/SelectedFeatureHandler" @@ -14,6 +12,7 @@ import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource" import MetaTagRecalculator from "../FeatureSource/Actors/MetaTagRecalculator" import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import ShowDataLayer from "../../UI/Map/ShowDataLayer" export default class FeaturePipelineState extends MapState { /** @@ -116,14 +115,12 @@ export default class FeaturePipelineState extends MapState { [self.currentBounds, source.layer.isDisplayed, sourceBBox] ) - new ShowDataLayer({ + new ShowDataLayer(self.maplibreMap, { features: source, - leafletMap: self.leafletMap, - layerToShow: source.layer.layerDef, + layer: source.layer.layerDef, doShowLayer: doShowFeatures, selectedElement: self.selectedElement, - state: self, - popup: (tags, layer) => self.CreatePopup(tags, layer), + buildPopup: (tags, layer) => self.CreatePopup(tags, layer), }) } @@ -136,8 +133,6 @@ export default class FeaturePipelineState extends MapState { sourcesToRegister.forEach((source) => self.metatagRecalculator.registerSource(source)) new SelectedFeatureHandler(Hash.hash, this) - - this.AddClusteringToMap(this.leafletMap) } public CreatePopup(tags: UIEventSource, layer: LayerConfig): ScrollableFullScreen { @@ -148,27 +143,4 @@ export default class FeaturePipelineState extends MapState { this.popups.set(tags.data.id, popup) return popup } - - /** - * Adds the cluster-tiles to the given map - * @param leafletMap: a UIEventSource possible having a leaflet map - * @constructor - */ - public AddClusteringToMap(leafletMap: UIEventSource) { - const clustering = this.layoutToUse.clustering - const self = this - new ShowDataLayer({ - features: this.featureAggregator.getCountsForZoom( - clustering, - this.locationControl, - clustering.minNeededElements - ), - leafletMap: leafletMap, - layerToShow: ShowTileInfo.styling, - popup: this.featureSwitchIsDebugging.data - ? (tags, layer) => new FeatureInfoBox(tags, layer, self) - : undefined, - state: this, - }) - } } diff --git a/Logic/State/GeoLocationState.ts b/Logic/State/GeoLocationState.ts index b6bd13e249..f0a9566be2 100644 --- a/Logic/State/GeoLocationState.ts +++ b/Logic/State/GeoLocationState.ts @@ -30,7 +30,7 @@ export class GeoLocationState { /** * If true: the map will center (and re-center) to this location */ - public readonly isLocked: UIEventSource = new UIEventSource(false) + public readonly allowMoving: UIEventSource = new UIEventSource(true) public readonly currentGPSLocation: UIEventSource = new UIEventSource(undefined) @@ -72,7 +72,6 @@ export class GeoLocationState { self._previousLocationGrant.setData("false") } }) - console.log("Previous location grant:", this._previousLocationGrant.data) if (this._previousLocationGrant.data === "true") { // A previous visit successfully granted permission. Chance is high that we are allowed to use it again! @@ -87,7 +86,6 @@ export class GeoLocationState { } this.requestPermission() } - window["geolocation_state"] = this } /** diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts index 724797585a..ad29cfaa6f 100644 --- a/Logic/State/MapState.ts +++ b/Logic/State/MapState.ts @@ -1,51 +1,31 @@ -import UserRelatedState from "./UserRelatedState" -import { Store, Stores, UIEventSource } from "../UIEventSource" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" +import { Store, UIEventSource } from "../UIEventSource" import Attribution from "../../UI/BigComponents/Attribution" -import Minimap, { MinimapObj } from "../../UI/Base/Minimap" -import { Tiles } from "../../Models/TileRange" import BaseUIElement from "../../UI/BaseUIElement" import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" import { QueryParameters } from "../Web/QueryParameters" import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer" import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource" -import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource" import { LocalStorageSource } from "../Web/LocalStorageSource" -import { GeoOperations } from "../GeoOperations" import TitleHandler from "../Actors/TitleHandler" import { BBox } from "../BBox" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { TiledStaticFeatureSource } from "../FeatureSource/Sources/StaticFeatureSource" -import { Translation, TypedTranslation } from "../../UI/i18n/Translation" -import { Tag } from "../Tags/Tag" +import StaticFeatureSource, { + TiledStaticFeatureSource, +} from "../FeatureSource/Sources/StaticFeatureSource" import { OsmConnection } from "../Osm/OsmConnection" -import { Feature, LineString } from "geojson" -import { OsmTags } from "../../Models/OsmFeature" - -export interface GlobalFilter { - filter: FilterState - id: string - onNewPoint: { - safetyCheck: Translation - confirmAddNew: TypedTranslation<{ preset: Translation }> - tags: Tag[] - } -} +import { Feature } from "geojson" +import { Map as MlMap } from "maplibre-gl" +import { GlobalFilter } from "../../Models/GlobalFilter" +import { MapProperties } from "../../Models/MapProperties" +import ShowDataLayer from "../../UI/Map/ShowDataLayer" /** * Contains all the leaflet-map related state */ -export default class MapState extends UserRelatedState { - /** - The leaflet instance of the big basemap - */ - public leafletMap = new UIEventSource(undefined, "leafletmap") +export default class MapState { + - /** - * The current background layer - */ - public backgroundLayer: UIEventSource /** * Last location where a click was registered */ @@ -58,34 +38,6 @@ export default class MapState extends UserRelatedState { * The bounds of the current map view */ public currentView: FeatureSourceForLayer & Tiled - /** - * The location as delivered by the GPS - */ - public currentUserLocation: SimpleFeatureSource - - /** - * All previously visited points, with their metadata - */ - public historicalUserLocations: SimpleFeatureSource - /** - * The number of seconds that the GPS-locations are stored in memory. - * Time in seconds - */ - public gpsLocationHistoryRetentionTime = new UIEventSource( - 7 * 24 * 60 * 60, - "gps_location_retention" - ) - /** - * A featureSource containing a single linestring which has the GPS-history of the user. - * However, metadata (such as when every single point was visited) is lost here (but is kept in `historicalUserLocations`. - * Note that this featureSource is _derived_ from 'historicalUserLocations' - */ - public historicalUserLocationsTrack: FeatureSourceForLayer & Tiled - - /** - * A feature source containing the current home location of the user - */ - public homeLocation: FeatureSourceForLayer & Tiled /** * A builtin layer which contains the selected element. @@ -94,7 +46,7 @@ export default class MapState extends UserRelatedState { */ public selectedElementsLayer: FeatureSourceForLayer & Tiled - public readonly mainMapObject: BaseUIElement & MinimapObj + public readonly mainMapObject: BaseUIElement /** * Which layers are enabled in the current theme and what filters are applied onto them @@ -114,9 +66,7 @@ export default class MapState extends UserRelatedState { */ public overlayToggles: { config: TilesourceConfig; isDisplayed: UIEventSource }[] - constructor(layoutToUse: LayoutConfig, options?: { attemptLogin: true | boolean }) { - super(layoutToUse, options) - + constructor() { this.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(this.locationControl) let defaultLayer = AvailableBaseLayers.osmCarto @@ -130,13 +80,6 @@ export default class MapState extends UserRelatedState { this.backgroundLayer = new UIEventSource(defaultLayer) this.backgroundLayer.addCallbackAndRunD((layer) => self.backgroundLayerId.setData(layer.id)) - const attr = new Attribution( - this.locationControl, - this.osmConnection.userDetails, - this.layoutToUse, - this.currentBounds - ) - // Will write into this.leafletMap this.mainMapObject = Minimap.createMiniMap({ background: this.backgroundLayer, @@ -162,12 +105,8 @@ export default class MapState extends UserRelatedState { MapState.InitializeFilteredLayers(this.layoutToUse, this.osmConnection) ) - this.lockBounds() this.AddAllOverlaysToMap(this.leafletMap) - this.initHomeLocation() - this.initGpsLocation() - this.initUserLocationTrail() this.initCurrentView() this.initSelectedElement() @@ -189,17 +128,6 @@ export default class MapState extends UserRelatedState { } } - private lockBounds() { - const layout = this.layoutToUse - if (!layout?.lockLocation) { - return - } - console.warn("Locking the bounds to ", layout.lockLocation) - this.mainMapObject.installBounds( - new BBox(layout.lockLocation), - this.featureSwitchIsTesting.data - ) - } private initCurrentView() { let currentViewLayer: FilteredLayer = this.filteredLayers.data.filter( @@ -244,17 +172,6 @@ export default class MapState extends UserRelatedState { this.currentView = new TiledStaticFeatureSource(features, currentViewLayer) } - private initGpsLocation() { - // Initialize the gps layer data. This is emtpy for now, the actual writing happens in the Geolocationhandler - const gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter( - (l) => l.layerDef.id === "gps_location" - )[0] - if (gpsLayerDef === undefined) { - return - } - this.currentUserLocation = new SimpleFeatureSource(gpsLayerDef, Tiles.tile_index(0, 0, 0)) - } - private initSelectedElement() { const layerDef: FilteredLayer = this.filteredLayers.data.filter( (l) => l.layerDef.id === "selected_element" @@ -281,145 +198,6 @@ export default class MapState extends UserRelatedState { this.selectedElementsLayer = new TiledStaticFeatureSource(store, layerDef) } - private initUserLocationTrail() { - const features = LocalStorageSource.GetParsed<{ feature: any; freshness: Date }[]>( - "gps_location_history", - [] - ) - const now = new Date().getTime() - features.data = features.data - .map((ff) => ({ feature: ff.feature, freshness: new Date(ff.freshness) })) - .filter( - (ff) => - now - ff.freshness.getTime() < 1000 * this.gpsLocationHistoryRetentionTime.data - ) - features.ping() - const self = this - let i = 0 - this.currentUserLocation?.features?.addCallbackAndRunD(([location]) => { - if (location === undefined) { - return - } - - const previousLocation = features.data[features.data.length - 1] - if (previousLocation !== undefined) { - const d = GeoOperations.distanceBetween( - previousLocation.feature.geometry.coordinates, - location.feature.geometry.coordinates - ) - let timeDiff = Number.MAX_VALUE // in seconds - const olderLocation = features.data[features.data.length - 2] - if (olderLocation !== undefined) { - timeDiff = - (new Date(previousLocation.freshness).getTime() - - new Date(olderLocation.freshness).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)) - feature.properties.id = "gps/" + features.data.length - i++ - features.data.push({ feature, freshness: new Date() }) - features.ping() - }) - - let gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter( - (l) => l.layerDef.id === "gps_location_history" - )[0] - if (gpsLayerDef !== undefined) { - this.historicalUserLocations = new SimpleFeatureSource( - gpsLayerDef, - Tiles.tile_index(0, 0, 0), - features - ) - this.changes.setHistoricalUserLocations(this.historicalUserLocations) - } - - const asLine = features.map((allPoints) => { - if (allPoints === undefined || allPoints.length < 2) { - return [] - } - - const feature: Feature = { - type: "Feature", - properties: { - id: "location_track", - "_date:now": new Date().toISOString(), - }, - geometry: { - type: "LineString", - coordinates: allPoints.map((ff) => ff.feature.geometry.coordinates), - }, - } - - self.allElements.ContainingFeatures.set(feature.properties.id, feature) - - return [ - { - feature, - freshness: new Date(), - }, - ] - }) - let gpsLineLayerDef: FilteredLayer = this.filteredLayers.data.filter( - (l) => l.layerDef.id === "gps_track" - )[0] - if (gpsLineLayerDef !== undefined) { - this.historicalUserLocationsTrack = new TiledStaticFeatureSource( - asLine, - gpsLineLayerDef - ) - } - } - - private initHomeLocation() { - const empty = [] - const feature = Stores.ListStabilized( - this.osmConnection.userDetails.map((userDetails) => { - if (userDetails === undefined) { - return undefined - } - const home = userDetails.home - if (home === undefined) { - return undefined - } - return [home.lon, home.lat] - }) - ).map((homeLonLat) => { - if (homeLonLat === undefined) { - return empty - } - return [ - { - feature: { - type: "Feature", - properties: { - id: "home", - "user:home": "yes", - _lon: homeLonLat[0], - _lat: homeLonLat[1], - }, - geometry: { - type: "Point", - coordinates: homeLonLat, - }, - }, - freshness: new Date(), - }, - ] - }) - - const flayer = this.filteredLayers.data.filter((l) => l.layerDef.id === "home_location")[0] - if (flayer !== undefined) { - this.homeLocation = new TiledStaticFeatureSource(feature, flayer) - } - } - private static getPref( osmConnection: OsmConnection, key: string, diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index cbd0d889ac..c178aa984f 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -1,21 +1,17 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { OsmConnection } from "../Osm/OsmConnection" import { MangroveIdentity } from "../Web/MangroveReviews" -import { Store, UIEventSource } from "../UIEventSource" -import { QueryParameters } from "../Web/QueryParameters" +import { Store, Stores, UIEventSource } from "../UIEventSource" import Locale from "../../UI/i18n/Locale" -import ElementsState from "./ElementsState" -import SelectedElementTagsUpdater from "../Actors/SelectedElementTagsUpdater" import { Changes } from "../Osm/Changes" -import ChangeToElementsActor from "../Actors/ChangeToElementsActor" -import PendingChangesUploader from "../Actors/PendingChangesUploader" -import Maproulette from "../Maproulette" +import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" +import FeatureSource from "../FeatureSource/FeatureSource" /** * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, * which layers they enabled, ... */ -export default class UserRelatedState extends ElementsState { +export default class UserRelatedState { /** The user credentials */ @@ -29,29 +25,22 @@ export default class UserRelatedState extends ElementsState { */ public mangroveIdentity: MangroveIdentity - /** - * Maproulette connection - */ - public maprouletteConnection: Maproulette - public readonly installedUserThemes: Store public readonly showAllQuestionsAtOnce: UIEventSource + public readonly homeLocation: FeatureSource - constructor(layoutToUse: LayoutConfig, options?: { attemptLogin: true | boolean }) { - super(layoutToUse) + /** + * The number of seconds that the GPS-locations are stored in memory. + * Time in seconds + */ + public gpsLocationHistoryRetentionTime = new UIEventSource( + 7 * 24 * 60 * 60, + "gps_location_retention" + ) - this.osmConnection = new OsmConnection({ - dryRun: this.featureSwitchIsTesting, - fakeUser: this.featureSwitchFakeUser.data, - oauth_token: QueryParameters.GetQueryParameter( - "oauth_token", - undefined, - "Used to complete the login" - ), - osmConfiguration: <"osm" | "osm-test">this.featureSwitchApiURL.data, - attemptLogin: options?.attemptLogin, - }) + constructor(osmConnection: OsmConnection, availableLanguages?: string[]) { + this.osmConnection = osmConnection { const translationMode: UIEventSource = this.osmConnection.GetPreference("translation-mode") @@ -72,49 +61,22 @@ export default class UserRelatedState extends ElementsState { }) } - this.changes = new Changes(this, layoutToUse?.isLeftRightSensitive() ?? false) this.showAllQuestionsAtOnce = UIEventSource.asBoolean( this.osmConnection.GetPreference("show-all-questions", "false", { documentation: "Either 'true' or 'false'. If set, all questions will be shown all at once", }) ) - new ChangeToElementsActor(this.changes, this.allElements) - new PendingChangesUploader(this.changes, this.selectedElement) this.mangroveIdentity = new MangroveIdentity( this.osmConnection.GetLongPreference("identity", "mangrove") ) - this.maprouletteConnection = new Maproulette() + this.InitializeLanguage(availableLanguages) - if (layoutToUse?.hideFromOverview) { - this.osmConnection.isLoggedIn.addCallbackAndRunD((loggedIn) => { - if (loggedIn) { - this.osmConnection - .GetPreference("hidden-theme-" + layoutToUse?.id + "-enabled") - .setData("true") - return true - } - }) - } - - if (this.layoutToUse !== undefined && !this.layoutToUse.official) { - console.log("Marking unofficial theme as visited") - this.osmConnection.GetLongPreference("unofficial-theme-" + this.layoutToUse.id).setData( - JSON.stringify({ - id: this.layoutToUse.id, - icon: this.layoutToUse.icon, - title: this.layoutToUse.title.translations, - shortDescription: this.layoutToUse.shortDescription.translations, - definition: this.layoutToUse["definition"], - }) - ) - } - - this.InitializeLanguage() - new SelectedElementTagsUpdater(this) this.installedUserThemes = this.InitInstalledUserThemes() + + this.homeLocation = this.initHomeLocation() } public GetUnofficialTheme(id: string): @@ -159,26 +121,50 @@ export default class UserRelatedState extends ElementsState { } } - private InitializeLanguage() { - const layoutToUse = this.layoutToUse + public markLayoutAsVisited(layout: LayoutConfig) { + if (!layout) { + console.error("Trying to mark a layout as visited, but ", layout, " got passed") + return + } + if (layout.hideFromOverview) { + this.osmConnection.isLoggedIn.addCallbackAndRunD((loggedIn) => { + if (loggedIn) { + this.osmConnection + .GetPreference("hidden-theme-" + layout?.id + "-enabled") + .setData("true") + return true + } + }) + } + if (!layout.official) { + this.osmConnection.GetLongPreference("unofficial-theme-" + layout.id).setData( + JSON.stringify({ + id: layout.id, + icon: layout.icon, + title: layout.title.translations, + shortDescription: layout.shortDescription.translations, + definition: layout["definition"], + }) + ) + } + } + + private InitializeLanguage(availableLanguages?: string[]) { Locale.language.syncWith(this.osmConnection.GetPreference("language")) Locale.language.addCallback((currentLanguage) => { - if (layoutToUse === undefined) { - return - } if (Locale.showLinkToWeblate.data) { return true // Disable auto switching as we are in translators mode } - if (this.layoutToUse.language.indexOf(currentLanguage) < 0) { + if (availableLanguages?.indexOf(currentLanguage) < 0) { console.log( "Resetting language to", - layoutToUse.language[0], + availableLanguages[0], "as", currentLanguage, " is unsupported" ) // The current language is not supported -> switch to a supported one - Locale.language.setData(layoutToUse.language[0]) + Locale.language.setData(availableLanguages[0]) } }) Locale.language.ping() @@ -193,4 +179,43 @@ export default class UserRelatedState extends ElementsState { .map((k) => k.substring(prefix.length, k.length - postfix.length)) ) } + + private initHomeLocation(): FeatureSource { + const empty = [] + const feature = Stores.ListStabilized( + this.osmConnection.userDetails.map((userDetails) => { + if (userDetails === undefined) { + return undefined + } + const home = userDetails.home + if (home === undefined) { + return undefined + } + return [home.lon, home.lat] + }) + ).map((homeLonLat) => { + if (homeLonLat === undefined) { + return empty + } + return [ + { + feature: { + type: "Feature", + properties: { + id: "home", + "user:home": "yes", + _lon: homeLonLat[0], + _lat: homeLonLat[1], + }, + geometry: { + type: "Point", + coordinates: homeLonLat, + }, + }, + freshness: new Date(), + }, + ] + }) + return new StaticFeatureSource(feature) + } } diff --git a/Models/GlobalFilter.ts b/Models/GlobalFilter.ts new file mode 100644 index 0000000000..c57a818a7d --- /dev/null +++ b/Models/GlobalFilter.ts @@ -0,0 +1,13 @@ +import { Translation, TypedTranslation } from "../UI/i18n/Translation" +import { FilterState } from "./FilteredLayer" +import { Tag } from "../Logic/Tags/Tag" + +export interface GlobalFilter { + filter: FilterState + id: string + onNewPoint: { + safetyCheck: Translation + confirmAddNew: TypedTranslation<{ preset: Translation }> + tags: Tag[] + } +} diff --git a/Models/LeafletMap.ts b/Models/LeafletMap.ts deleted file mode 100644 index e3f0ae8502..0000000000 --- a/Models/LeafletMap.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface LeafletMap { - getBounds(): [[number, number], [number, number]] -} diff --git a/Models/MapProperties.ts b/Models/MapProperties.ts new file mode 100644 index 0000000000..9ac228f574 --- /dev/null +++ b/Models/MapProperties.ts @@ -0,0 +1,14 @@ +import { Store, UIEventSource } from "../Logic/UIEventSource" +import { BBox } from "../Logic/BBox" +import { RasterLayerPolygon } from "./RasterLayers" + +export interface MapProperties { + readonly location: UIEventSource<{ lon: number; lat: number }> + readonly zoom: UIEventSource + readonly bounds: Store + readonly rasterLayer: UIEventSource + + readonly maxbounds: UIEventSource + + readonly allowMoving: UIEventSource +} diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index aaf84bc3ae..0bb635ef6d 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -23,8 +23,6 @@ import predifined_filters from "../../../assets/layers/filters/filters.json" import { TagConfigJson } from "../Json/TagConfigJson" import PointRenderingConfigJson from "../Json/PointRenderingConfigJson" import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" -import { type } from "os" -import exp from "constants" class ExpandFilter extends DesugaringStep { private static readonly predefinedFilters = ExpandFilter.load_filters() diff --git a/Models/ThemeConfig/Conversion/PrepareTheme.ts b/Models/ThemeConfig/Conversion/PrepareTheme.ts index f65a768da2..15e41503f4 100644 --- a/Models/ThemeConfig/Conversion/PrepareTheme.ts +++ b/Models/ThemeConfig/Conversion/PrepareTheme.ts @@ -175,7 +175,7 @@ class SubstituteLayer extends Conversion { - private _state: DesugaringContext + private readonly _state: DesugaringContext constructor(state: DesugaringContext) { super( @@ -430,7 +430,7 @@ class AddDependencyLayersToTheme extends DesugaringStep { constructor(state: DesugaringContext) { super( `If a layer has a dependency on another layer, these layers are added automatically on the theme. (For example: defibrillator depends on 'walls_and_buildings' to snap onto. This layer is added automatically) - + Note that these layers are added _at the start_ of the layer list, meaning that they will see _every_ feature. Furthermore, \`passAllFeatures\` will be set, so that they won't steal away features from further layers. Some layers (e.g. \`all_buildings_and_walls\' or \'streets_with_a_name\') are invisible, so by default, \'force_load\' is set too. diff --git a/Models/ThemeConfig/Conversion/Validation.ts b/Models/ThemeConfig/Conversion/Validation.ts index 5e5a92d341..765b095d68 100644 --- a/Models/ThemeConfig/Conversion/Validation.ts +++ b/Models/ThemeConfig/Conversion/Validation.ts @@ -15,6 +15,7 @@ import Svg from "../../../Svg" import FilterConfigJson from "../Json/FilterConfigJson" import DeleteConfig from "../DeleteConfig" import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" +import ValidatedTextField from "../../../UI/Input/ValidatedTextField" class ValidateLanguageCompleteness extends DesugaringStep { private readonly _languages: string[] @@ -594,6 +595,7 @@ export class DetectMappingsWithImages extends DesugaringStep { private _options: { noQuestionHintCheck: boolean } + constructor(options: { noQuestionHintCheck: boolean }) { super("Miscellaneous checks on the tagrendering", ["special"], "MiscTagRenderingChecks") this._options = options @@ -637,6 +639,19 @@ class MiscTagRenderingChecks extends DesugaringStep { } } } + const freeformType = json["freeform"]?.["type"] + if (freeformType) { + if (ValidatedTextField.AvailableTypes().indexOf(freeformType) < 0) { + throw ( + "At " + + context + + ".freeform.type is an unknown type: " + + freeformType + + "; try one of " + + ValidatedTextField.AvailableTypes().join(", ") + ) + } + } return { result: json, errors, @@ -905,6 +920,38 @@ export class ValidateLayer extends DesugaringStep { } } +export class ValidateFilter extends DesugaringStep { + constructor() { + super("Detect common errors in the filters", [], "ValidateFilter") + } + + convert( + filter: FilterConfigJson, + context: string + ): { + result: FilterConfigJson + errors?: string[] + warnings?: string[] + information?: string[] + } { + const errors = [] + for (const option of filter.options) { + for (let i = 0; i < option.fields.length; i++) { + const field = option.fields[i] + const type = field.type ?? "string" + if (!ValidatedTextField.ForType(type) !== undefined) { + continue + } + const err = `Invalid filter: ${type} is not a valid validated textfield type (at ${context}.fields[${i}])\n\tTry one of ${Array.from( + ValidatedTextField.AvailableTypes() + ).join(",")}` + errors.push(err) + } + } + return { result: filter, errors } + } +} + export class DetectDuplicateFilters extends DesugaringStep<{ layers: LayerConfigJson[] themes: LayoutConfigJson[] diff --git a/Models/ThemeConfig/FilterConfig.ts b/Models/ThemeConfig/FilterConfig.ts index 989d49f5b7..4be92468d3 100644 --- a/Models/ThemeConfig/FilterConfig.ts +++ b/Models/ThemeConfig/FilterConfig.ts @@ -3,7 +3,6 @@ import { TagsFilter } from "../../Logic/Tags/TagsFilter" import FilterConfigJson from "./Json/FilterConfigJson" import Translations from "../../UI/i18n/Translations" import { TagUtils } from "../../Logic/Tags/TagUtils" -import ValidatedTextField from "../../UI/Input/ValidatedTextField" import { TagConfigJson } from "./Json/TagConfigJson" import { UIEventSource } from "../../Logic/UIEventSource" import { FilterState } from "../FilteredLayer" @@ -54,11 +53,7 @@ export default class FilterConfig { const fields: { name: string; type: string }[] = (option.fields ?? []).map((f, i) => { const type = f.type ?? "string" - if (!ValidatedTextField.ForType(type) === undefined) { - throw `Invalid filter: ${type} is not a valid validated textfield type (at ${ctx}.fields[${i}])\n\tTry one of ${Array.from( - ValidatedTextField.AvailableTypes() - ).join(",")}` - } + // Type is validated against 'ValidatedTextField' in Validation.ts, in ValidateFilterConfig if (f.name === undefined || f.name === "" || f.name.match(/[a-z0-9_-]+/) == null) { throw `Invalid filter: a variable name should match [a-z0-9_-]+ at ${ctx}.fields[${i}]` } diff --git a/Models/ThemeConfig/Json/LayerConfigJson.ts b/Models/ThemeConfig/Json/LayerConfigJson.ts index 69e132e8a0..67a6f5571a 100644 --- a/Models/ThemeConfig/Json/LayerConfigJson.ts +++ b/Models/ThemeConfig/Json/LayerConfigJson.ts @@ -40,8 +40,9 @@ export interface LayerConfigJson { * * Every source _must_ define which tags _must_ be present in order to be picked up. * + * Note: a source must always be defined. 'special' is only allowed if this is a builtin-layer */ - source: { + source: "special" | "special:library" | ({ /** * Every source must set which tags have to be present in order to load the given layer. */ @@ -102,7 +103,7 @@ export interface LayerConfigJson { * Setting this key will rename this field into 'id' */ idKey?: string - } + }) ) /** diff --git a/Models/ThemeConfig/Json/PointRenderingConfigJson.ts b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts index 7fe5e62e81..17e243924e 100644 --- a/Models/ThemeConfig/Json/PointRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts @@ -12,8 +12,11 @@ import { TagConfigJson } from "./TagConfigJson" export default interface PointRenderingConfigJson { /** * All the locations that this point should be rendered at. - * Using `location: ["point", "centroid"] will always render centerpoint. - * 'projected_centerpoint' will show an item on the line itself, near the middle of the line. (LineStrings only) + * Possible values are: + * - `point`: only renders points at their location + * - `centroid`: show a symbol at the centerpoint of a (multi)Linestring and (multi)polygon. Points will _not_ be rendered with this + * - `projected_centerpoint`: Only on (multi)linestrings: calculate the centerpoint and snap it to the way + * - `start` and `end`: only on linestrings: add a point to the first/last coordinate of the LineString */ location: ("point" | "centroid" | "start" | "end" | "projected_centerpoint" | string)[] diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index 24fc7bb144..03be4abee6 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -29,7 +29,7 @@ import { Overpass } from "../../Logic/Osm/Overpass" import Constants from "../Constants" import { FixedUiElement } from "../../UI/Base/FixedUiElement" import Svg from "../../Svg" -import { UIEventSource } from "../../Logic/UIEventSource" +import { ImmutableStore } from "../../Logic/UIEventSource" import { OsmTags } from "../OsmFeature" export default class LayerConfig extends WithContextLoader { @@ -37,7 +37,10 @@ export default class LayerConfig extends WithContextLoader { public readonly id: string public readonly name: Translation public readonly description: Translation - public readonly source: SourceConfig + /** + * Only 'null' for special, privileged layers + */ + public readonly source: SourceConfig | null public readonly calculatedTags: [string, string, boolean][] public readonly doNotDownload: boolean public readonly passAllFeatures: boolean @@ -83,7 +86,9 @@ export default class LayerConfig extends WithContextLoader { throw "Layer " + this.id + " does not define a source section (" + context + ")" } - if (json.source.osmTags === undefined) { + if(json.source === "special" || json.source === "special:library"){ + this.source = null + }else if (json.source.osmTags === undefined) { throw ( "Layer " + this.id + @@ -584,11 +589,9 @@ export default class LayerConfig extends WithContextLoader { .filter((mr) => mr.location.has("point")) .map( (mr) => - mr.GenerateLeafletStyle( - new UIEventSource({ id: "node/-1" }), - false, - { includeBadges: false } - ).html + mr.RenderIcon(new ImmutableStore({ id: "node/-1" }), false, { + includeBadges: false, + }).html ) .find((i) => i !== undefined) } diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts index 4763169dee..4068b4fe83 100644 --- a/Models/ThemeConfig/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -8,7 +8,6 @@ import { ExtractImages } from "./Conversion/FixImages" import ExtraLinkConfig from "./ExtraLinkConfig" import { Utils } from "../../Utils" import LanguageUtils from "../../Utils/LanguageUtils" - /** * Minimal information about a theme **/ diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts index d6a3317df3..9e0fdd5590 100644 --- a/Models/ThemeConfig/PointRenderingConfig.ts +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -175,7 +175,7 @@ export default class PointRenderingConfig extends WithContextLoader { ) } - public GenerateLeafletStyle( + public RenderIcon( tags: Store, clickable: boolean, options?: { @@ -210,7 +210,7 @@ export default class PointRenderingConfig extends WithContextLoader { // in MapLibre, the offset is relative to the _center_ of the object, with left = [-x, 0] and up = [0,-y] let anchorW = 0 - let anchorH = iconH / 2 + let anchorH = 0 if (mode === "left") { anchorW = -iconW / 2 } diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index a20011d74d..69b75609ac 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -3,7 +3,6 @@ import { TagsFilter } from "../../Logic/Tags/TagsFilter" import Translations from "../../UI/i18n/Translations" import { TagUtils, UploadableTag } from "../../Logic/Tags/TagUtils" import { And } from "../../Logic/Tags/And" -import ValidatedTextField from "../../UI/Input/ValidatedTextField" import { Utils } from "../../Utils" import { Tag } from "../../Logic/Tags/Tag" import BaseUIElement from "../../UI/BaseUIElement" @@ -132,17 +131,6 @@ export default class TagRenderingConfig { } const type = json.freeform.type ?? "string" - if (ValidatedTextField.AvailableTypes().indexOf(type) < 0) { - throw ( - "At " + - context + - ".freeform.type is an unknown type: " + - type + - "; try one of " + - ValidatedTextField.AvailableTypes().join(", ") - ) - } - let placeholder: Translation = Translations.T(json.freeform.placeholder) if (placeholder === undefined) { const typeDescription = Translations.t.validation[type]?.description @@ -182,13 +170,7 @@ export default class TagRenderingConfig { } } - if ( - this.freeform.type !== undefined && - ValidatedTextField.AvailableTypes().indexOf(this.freeform.type) < 0 - ) { - const knownKeys = ValidatedTextField.AvailableTypes().join(", ") - throw `Freeform.key ${this.freeform.key} is an invalid type. Known keys are ${knownKeys}` - } + // freeform.type is validated in Validation.ts so that we don't need ValidatedTextFields here if (this.freeform.addExtraTags) { const usedKeys = new And(this.freeform.addExtraTags).usedKeys() if (usedKeys.indexOf(this.freeform.key) >= 0) { diff --git a/UI/AutomatonGui.ts b/UI/AutomatonGui.ts deleted file mode 100644 index 1a8b446a8d..0000000000 --- a/UI/AutomatonGui.ts +++ /dev/null @@ -1,476 +0,0 @@ -import BaseUIElement from "./BaseUIElement" -import Combine from "./Base/Combine" -import Svg from "../Svg" -import Title from "./Base/Title" -import Toggle from "./Input/Toggle" -import { SubtleButton } from "./Base/SubtleButton" -import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" -import ValidatedTextField from "./Input/ValidatedTextField" -import { Utils } from "../Utils" -import { UIEventSource } from "../Logic/UIEventSource" -import { VariableUiElement } from "./Base/VariableUIElement" -import { FixedUiElement } from "./Base/FixedUiElement" -import { Tiles } from "../Models/TileRange" -import { LocalStorageSource } from "../Logic/Web/LocalStorageSource" -import { DropDown } from "./Input/DropDown" -import { AllKnownLayouts } from "../Customizations/AllKnownLayouts" -import MinimapImplementation from "./Base/MinimapImplementation" -import { OsmConnection } from "../Logic/Osm/OsmConnection" -import { BBox } from "../Logic/BBox" -import MapState from "../Logic/State/MapState" -import FeaturePipeline from "../Logic/FeatureSource/FeaturePipeline" -import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" -import FeatureSource from "../Logic/FeatureSource/FeatureSource" -import List from "./Base/List" -import { QueryParameters } from "../Logic/Web/QueryParameters" -import { SubstitutedTranslation } from "./SubstitutedTranslation" -import { AutoAction } from "./Popup/AutoApplyButton" -import DynamicGeoJsonTileSource from "../Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource" -import themeOverview from "../assets/generated/theme_overview.json" - -class AutomationPanel extends Combine { - private static readonly openChangeset = new UIEventSource(undefined) - - constructor( - layoutToUse: LayoutConfig, - indices: number[], - extraCommentText: UIEventSource, - tagRenderingToAutomate: { layer: LayerConfig; tagRendering: TagRenderingConfig } - ) { - const layerId = tagRenderingToAutomate.layer.id - const trId = tagRenderingToAutomate.tagRendering.id - const tileState = LocalStorageSource.GetParsed( - "automation-tile_state-" + layerId + "-" + trId, - {} - ) - const logMessages = new UIEventSource([]) - if (indices === undefined) { - throw "No tiles loaded - can not automate" - } - const openChangeset = AutomationPanel.openChangeset - - openChangeset.addCallbackAndRun((cs) => - console.trace("Sync current open changeset to:", cs) - ) - - const nextTileToHandle = tileState.map((handledTiles) => { - for (const index of indices) { - if (handledTiles[index] !== undefined) { - // Already handled - continue - } - return index - } - return undefined - }) - nextTileToHandle.addCallback((t) => console.warn("Next tile to handle is", t)) - - const neededTimes = new UIEventSource([]) - const automaton = new VariableUiElement( - nextTileToHandle.map((tileIndex) => { - if (tileIndex === undefined) { - return new FixedUiElement("All done!").SetClass("thanks") - } - console.warn("Triggered map on nextTileToHandle", tileIndex) - const start = new Date() - return AutomationPanel.TileHandler( - layoutToUse, - tileIndex, - layerId, - tagRenderingToAutomate.tagRendering, - extraCommentText, - (result, logMessage) => { - const end = new Date() - const timeNeeded = (end.getTime() - start.getTime()) / 1000 - neededTimes.data.push(timeNeeded) - neededTimes.ping() - tileState.data[tileIndex] = result - tileState.ping() - if (logMessage !== undefined) { - logMessages.data.push(logMessage) - logMessages.ping() - } - } - ) - }) - ) - - const statistics = new VariableUiElement( - tileState.map((states) => { - let total = 0 - const perResult = new Map() - for (const key in states) { - total++ - const result = states[key] - perResult.set(result, (perResult.get(result) ?? 0) + 1) - } - - let sum = 0 - neededTimes.data.forEach((v) => { - sum = sum + v - }) - let timePerTile = sum / neededTimes.data.length - - return new Combine([ - "Handled " + total + "/" + indices.length + " tiles: ", - new List( - Array.from(perResult.keys()).map((key) => key + ": " + perResult.get(key)) - ), - "Handling one tile needs " + - Math.floor(timePerTile * 100) / 100 + - "s on average. Estimated time left: " + - Utils.toHumanTime((indices.length - total) * timePerTile), - ]).SetClass("flex flex-col") - }) - ) - - super([ - statistics, - automaton, - new SubtleButton(undefined, "Clear fixed").onClick(() => { - const st = tileState.data - for (const tileIndex in st) { - if (st[tileIndex] === "fixed") { - delete st[tileIndex] - } - } - - tileState.ping() - }), - new VariableUiElement(logMessages.map((logMessages) => new List(logMessages))), - ]) - this.SetClass("flex flex-col") - } - - private static TileHandler( - layoutToUse: LayoutConfig, - tileIndex: number, - targetLayer: string, - targetAction: TagRenderingConfig, - extraCommentText: UIEventSource, - whenDone: (result: string, logMessage?: string) => void - ): BaseUIElement { - const state = new MapState(layoutToUse, { attemptLogin: false }) - extraCommentText.syncWith(state.changes.extraComment) - const [z, x, y] = Tiles.tile_from_index(tileIndex) - state.locationControl.setData({ - zoom: z, - lon: x, - lat: y, - }) - state.currentBounds.setData(BBox.fromTileIndex(tileIndex)) - - let targetTiles: UIEventSource = new UIEventSource([]) - const pipeline = new FeaturePipeline((tile) => { - const layerId = tile.layer.layerDef.id - if (layerId === targetLayer) { - targetTiles.data.push(tile) - targetTiles.ping() - } - }, state) - - state.locationControl.ping() - state.currentBounds.ping() - const stateToShow = new UIEventSource("") - - pipeline.runningQuery.map( - async (isRunning) => { - if (targetTiles.data.length === 0) { - stateToShow.setData("No data loaded yet...") - return - } - if (isRunning) { - stateToShow.setData( - "Waiting for all layers to be loaded... Has " + - targetTiles.data.length + - " tiles already" - ) - return - } - if (targetTiles.data.length === 0) { - stateToShow.setData("No features found to apply the action") - whenDone("empty") - return true - } - stateToShow.setData("Gathering applicable elements") - - let handled = 0 - let inspected = 0 - let log = [] - for (const targetTile of targetTiles.data) { - for (const ffs of targetTile.features.data) { - inspected++ - if (inspected % 10 === 0) { - stateToShow.setData( - "Inspected " + - inspected + - " features, updated " + - handled + - " features" - ) - } - const feature = ffs.feature - const renderingTr = targetAction.GetRenderValue(feature.properties) - const rendering = renderingTr.txt - log.push( - "" + - feature.properties.id + - ": " + - new SubstitutedTranslation( - renderingTr, - new UIEventSource(feature.properties), - undefined - ).ConstructElement().textContent - ) - const actions = Utils.NoNull( - SubstitutedTranslation.ExtractSpecialComponents(rendering).map( - (obj) => obj.special - ) - ) - for (const action of actions) { - const auto = action.func - if (auto.supportsAutoAction !== true) { - continue - } - - await auto.applyActionOn( - { - layoutToUse: state.layoutToUse, - changes: state.changes, - }, - state.allElements.getEventSourceById(feature.properties.id), - action.args - ) - handled++ - } - } - } - stateToShow.setData( - "Done! Inspected " + inspected + " features, updated " + handled + " features" - ) - - if (inspected === 0) { - whenDone("empty") - return true - } - - if (handled === 0) { - whenDone("no-action", "Inspected " + inspected + " elements: " + log.join("; ")) - } else { - state.osmConnection.AttemptLogin() - state.changes.flushChanges("handled tile automatically, time to flush!") - whenDone( - "fixed", - "Updated " + - handled + - " elements, inspected " + - inspected + - ": " + - log.join("; ") - ) - } - return true - }, - [targetTiles] - ) - - return new Combine([ - new Title("Performing action for tile " + tileIndex, 1), - new VariableUiElement(stateToShow), - ]).SetClass("flex flex-col") - } -} - -class AutomatonGui { - constructor() { - const osmConnection = new OsmConnection({ - singlePage: false, - oauth_token: QueryParameters.GetQueryParameter("oauth_token", "OAuth token"), - }) - - new Combine([ - new Combine([ - Svg.robot_svg().SetClass("w-24 h-24 p-4 rounded-full subtle-background"), - new Combine([ - new Title("MapComplete Automaton", 1), - "This page helps to automate certain tasks for a theme. Expert use only.", - ]).SetClass("flex flex-col m-4"), - ]).SetClass("flex"), - new Toggle( - AutomatonGui.GenerateMainPanel(), - new SubtleButton(Svg.osm_logo_svg(), "Login to get started").onClick(() => - osmConnection.AttemptLogin() - ), - osmConnection.isLoggedIn - ), - ]) - .SetClass("block p-4") - .AttachTo("main") - } - - private static GenerateMainPanel(): BaseUIElement { - const themeSelect = new DropDown( - "Select a theme", - Array.from(themeOverview).map((l) => ({ value: l.id, shown: l.id })) - ) - - LocalStorageSource.Get("automation-theme-id", "missing_streets").syncWith( - themeSelect.GetValue() - ) - - const tilepath = ValidatedTextField.ForType("url").ConstructInputElement({ - placeholder: "Specifiy the path of the overview", - inputStyle: "width: 100%", - }) - tilepath.SetClass("w-full") - LocalStorageSource.Get("automation-tile_path").syncWith(tilepath.GetValue(), true) - - let tilesToRunOver = tilepath.GetValue().bind((path) => { - if (path === undefined) { - return undefined - } - return UIEventSource.FromPromiseWithErr(Utils.downloadJsonCached(path, 1000 * 60 * 60)) - }) - - const targetZoom = 14 - - const tilesPerIndex = tilesToRunOver.map((tiles) => { - if (tiles === undefined || tiles["error"] !== undefined) { - return undefined - } - let indexes: number[] = [] - const tilesS = tiles["success"] - DynamicGeoJsonTileSource.RegisterWhitelist(tilepath.GetValue().data, tilesS) - const z = Number(tilesS["zoom"]) - for (const key in tilesS) { - if (key === "zoom") { - continue - } - const x = Number(key) - const ys = tilesS[key] - indexes.push(...ys.map((y) => Tiles.tile_index(z, x, y))) - } - - console.log("Got ", indexes.length, "indexes") - let rezoomed = new Set() - for (const index of indexes) { - let [z, x, y] = Tiles.tile_from_index(index) - while (z > targetZoom) { - z-- - x = Math.floor(x / 2) - y = Math.floor(y / 2) - } - rezoomed.add(Tiles.tile_index(z, x, y)) - } - - return Array.from(rezoomed) - }) - - const extraComment = ValidatedTextField.ForType("text").ConstructInputElement() - LocalStorageSource.Get("automaton-extra-comment").syncWith(extraComment.GetValue()) - - return new Combine([ - themeSelect, - "Specify the path to a tile overview. This is a hosted .json of the format {x : [y0, y1, y2], x1: [y0, ...]} where x is a string and y are numbers", - tilepath, - "Add an extra comment:", - extraComment, - new VariableUiElement( - extraComment - .GetValue() - .map((c) => "Your comment is " + (c?.length ?? 0) + "/200 characters long") - ).SetClass("subtle"), - new VariableUiElement( - tilesToRunOver.map((t) => { - if (t === undefined) { - return "No path given or still loading..." - } - if (t["error"] !== undefined) { - return new FixedUiElement("Invalid URL or data: " + t["error"]).SetClass( - "alert" - ) - } - - return new FixedUiElement( - "Loaded " + tilesPerIndex.data.length + " tiles to automated over" - ).SetClass("thanks") - }) - ), - new VariableUiElement( - themeSelect - .GetValue() - .map((id) => AllKnownLayouts.allKnownLayouts.get(id)) - .map( - (layoutToUse) => { - if (layoutToUse === undefined) { - return new FixedUiElement("Select a valid layout") - } - if ( - tilesPerIndex.data === undefined || - tilesPerIndex.data.length === 0 - ) { - return "No tiles given" - } - - const automatableTagRenderings: { - layer: LayerConfig - tagRendering: TagRenderingConfig - }[] = [] - for (const layer of layoutToUse.layers) { - for (const tagRendering of layer.tagRenderings) { - if (tagRendering.group === "auto") { - automatableTagRenderings.push({ - layer, - tagRendering: tagRendering, - }) - } - } - } - console.log("Automatable tag renderings:", automatableTagRenderings) - if (automatableTagRenderings.length === 0) { - return new FixedUiElement( - 'This theme does not have any tagRendering with "group": "auto" set' - ).SetClass("alert") - } - const pickAuto = new DropDown("Pick the action to automate", [ - { - value: undefined, - shown: "Pick an option", - }, - ...automatableTagRenderings.map((config) => ({ - shown: config.layer.id + " - " + config.tagRendering.id, - value: config, - })), - ]) - - return new Combine([ - pickAuto, - new VariableUiElement( - pickAuto - .GetValue() - .map((auto) => - auto === undefined - ? undefined - : new AutomationPanel( - layoutToUse, - tilesPerIndex.data, - extraComment.GetValue(), - auto - ) - ) - ), - ]) - }, - [tilesPerIndex] - ) - ).SetClass("flex flex-col"), - ]).SetClass("flex flex-col") - } -} - -MinimapImplementation.initialize() - -new AutomatonGui() diff --git a/UI/Base/If.svelte b/UI/Base/If.svelte new file mode 100644 index 0000000000..124ff330bf --- /dev/null +++ b/UI/Base/If.svelte @@ -0,0 +1,14 @@ + + +{#if _c} + +{/if} diff --git a/UI/Base/MapControlButton.svelte b/UI/Base/MapControlButton.svelte new file mode 100644 index 0000000000..fd87d3d244 --- /dev/null +++ b/UI/Base/MapControlButton.svelte @@ -0,0 +1,13 @@ + + + +
dispatch("click", e)} class="subtle-background block rounded-full min-w-10 h-10 pointer-events-auto m-0.5 md:m-1 p-1"> + +
diff --git a/UI/Base/Minimap.ts b/UI/Base/Minimap.ts deleted file mode 100644 index 13ac4b5f7f..0000000000 --- a/UI/Base/Minimap.ts +++ /dev/null @@ -1,47 +0,0 @@ -import BaseUIElement from "../BaseUIElement" -import Loc from "../../Models/Loc" -import BaseLayer from "../../Models/BaseLayer" -import { UIEventSource } from "../../Logic/UIEventSource" -import { BBox } from "../../Logic/BBox" - -export interface MinimapOptions { - background?: UIEventSource - location?: UIEventSource - bounds?: UIEventSource - allowMoving?: boolean - leafletOptions?: any - attribution?: BaseUIElement | boolean - onFullyLoaded?: (leaflet: L.Map) => void - leafletMap?: UIEventSource - lastClickLocation?: UIEventSource<{ lat: number; lon: number }> - addLayerControl?: boolean | false -} - -export interface MinimapObj { - readonly leafletMap: UIEventSource - readonly location: UIEventSource - readonly bounds: UIEventSource - - installBounds(factor: number | BBox, showRange?: boolean): void - - TakeScreenshot(format): Promise - TakeScreenshot(format: "image"): Promise - TakeScreenshot(format: "blob"): Promise - TakeScreenshot(format?: "image" | "blob"): Promise -} - -export default class Minimap { - /** - * A stub implementation. The actual implementation is injected later on, but only in the browser. - * importing leaflet crashes node-ts, which is pretty annoying considering the fact that a lot of scripts use it - */ - - private constructor() {} - - /** - * Construct a minimap - */ - public static createMiniMap: (options?: MinimapOptions) => BaseUIElement & MinimapObj = (_) => { - throw "CreateMinimap hasn't been initialized yet. Please call MinimapImplementation.initialize()" - } -} diff --git a/UI/Base/MinimapImplementation.ts b/UI/Base/MinimapImplementation.ts deleted file mode 100644 index a830f6b1d3..0000000000 --- a/UI/Base/MinimapImplementation.ts +++ /dev/null @@ -1,422 +0,0 @@ -import { Utils } from "../../Utils" -import BaseUIElement from "../BaseUIElement" -import { UIEventSource } from "../../Logic/UIEventSource" -import Loc from "../../Models/Loc" -import BaseLayer from "../../Models/BaseLayer" -import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" -import * as L from "leaflet" -import { LeafletMouseEvent, Map } from "leaflet" -import Minimap, { MinimapObj, MinimapOptions } from "./Minimap" -import { BBox } from "../../Logic/BBox" -import "leaflet-polylineoffset" -import { SimpleMapScreenshoter } from "leaflet-simple-map-screenshoter" -import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch" -import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" -import ShowDataLayerImplementation from "../ShowDataLayer/ShowDataLayerImplementation" -import FilteredLayer from "../../Models/FilteredLayer" -import ScrollableFullScreen from "./ScrollableFullScreen" -import Constants from "../../Models/Constants" -import StrayClickHandler from "../../Logic/Actors/StrayClickHandler" - -/** - * The stray-click-hanlders adds a marker to the map if no feature was clicked. - * Shows the given uiToShow-element in the messagebox - */ -class StrayClickHandlerImplementation { - private _lastMarker - - constructor( - state: { - LastClickLocation: UIEventSource<{ lat: number; lon: number }> - selectedElement: UIEventSource - filteredLayers: UIEventSource - leafletMap: UIEventSource - }, - uiToShow: ScrollableFullScreen, - iconToShow: BaseUIElement - ) { - const self = this - const leafletMap = state.leafletMap - state.filteredLayers.data.forEach((filteredLayer) => { - filteredLayer.isDisplayed.addCallback((isEnabled) => { - if (isEnabled && self._lastMarker && leafletMap.data !== undefined) { - // When a layer is activated, we remove the 'last click location' in order to force the user to reclick - // This reclick might be at a location where a feature now appeared... - state.leafletMap.data.removeLayer(self._lastMarker) - } - }) - }) - - state.LastClickLocation.addCallback(function (lastClick) { - if (self._lastMarker !== undefined) { - state.leafletMap.data?.removeLayer(self._lastMarker) - } - - if (lastClick === undefined) { - return - } - - state.selectedElement.setData(undefined) - const clickCoor: [number, number] = [lastClick.lat, lastClick.lon] - self._lastMarker = L.marker(clickCoor, { - icon: L.divIcon({ - html: iconToShow.ConstructElement(), - iconSize: [50, 50], - iconAnchor: [25, 50], - popupAnchor: [0, -45], - }), - }) - - self._lastMarker.addTo(leafletMap.data) - - self._lastMarker.on("click", () => { - if (leafletMap.data.getZoom() < Constants.userJourney.minZoomLevelToAddNewPoints) { - leafletMap.data.flyTo( - clickCoor, - Constants.userJourney.minZoomLevelToAddNewPoints - ) - return - } - - uiToShow.Activate() - }) - }) - - state.selectedElement.addCallback(() => { - if (self._lastMarker !== undefined) { - leafletMap.data.removeLayer(self._lastMarker) - this._lastMarker = undefined - } - }) - } -} - -export default class MinimapImplementation extends BaseUIElement implements MinimapObj { - private static _nextId = 0 - public readonly leafletMap: UIEventSource - public readonly location: UIEventSource - public readonly bounds: UIEventSource | undefined - private readonly _id: string - private readonly _background: UIEventSource - private _isInited = false - private _allowMoving: boolean - private readonly _leafletoptions: any - private readonly _onFullyLoaded: (leaflet: L.Map) => void - private readonly _attribution: BaseUIElement | boolean - private readonly _addLayerControl: boolean - private readonly _options: MinimapOptions - - private constructor(options?: MinimapOptions) { - super() - options = options ?? {} - this._id = "minimap" + MinimapImplementation._nextId - MinimapImplementation._nextId++ - this.leafletMap = options.leafletMap ?? new UIEventSource(undefined) - this._background = - options?.background ?? new UIEventSource(AvailableBaseLayers.osmCarto) - this.location = options?.location ?? new UIEventSource({ lat: 0, lon: 0, zoom: 1 }) - this.bounds = options?.bounds - this._allowMoving = options.allowMoving ?? true - this._leafletoptions = options.leafletOptions ?? {} - this._onFullyLoaded = options.onFullyLoaded - this._attribution = options.attribution - this._addLayerControl = options.addLayerControl ?? false - this._options = options - this.SetClass("relative") - } - - public static initialize() { - Minimap.createMiniMap = (options) => new MinimapImplementation(options) - ShowDataLayer.actualContstructor = (options) => new ShowDataLayerImplementation(options) - StrayClickHandler.construct = ( - state: { - LastClickLocation: UIEventSource<{ lat: number; lon: number }> - selectedElement: UIEventSource - filteredLayers: UIEventSource - leafletMap: UIEventSource - }, - uiToShow: ScrollableFullScreen, - iconToShow: BaseUIElement - ) => { - return new StrayClickHandlerImplementation(state, uiToShow, iconToShow) - } - } - - public installBounds(factor: number | BBox, showRange?: boolean) { - this.leafletMap.addCallbackD((leaflet) => { - let bounds: { getEast(); getNorth(); getWest(); getSouth() } - if (typeof factor === "number") { - const lbounds = leaflet.getBounds().pad(factor) - leaflet.setMaxBounds(lbounds) - bounds = lbounds - } else { - // @ts-ignore - leaflet.setMaxBounds(factor.toLeaflet()) - bounds = factor - } - - if (showRange) { - const data = { - type: "FeatureCollection", - features: [ - { - type: "Feature", - geometry: { - type: "LineString", - coordinates: [ - [bounds.getEast(), bounds.getNorth()], - [bounds.getWest(), bounds.getNorth()], - [bounds.getWest(), bounds.getSouth()], - - [bounds.getEast(), bounds.getSouth()], - [bounds.getEast(), bounds.getNorth()], - ], - }, - }, - ], - } - // @ts-ignore - L.geoJSON(data, { - style: { - color: "#f44", - weight: 4, - opacity: 0.7, - }, - }).addTo(leaflet) - } - }) - } - - Destroy() { - super.Destroy() - console.warn("Decomissioning minimap", this._id) - const mp = this.leafletMap.data - this.leafletMap.setData(null) - mp.off() - mp.remove() - } - - /** - * Takes a screenshot of the current map - * @param format: image: give a base64 encoded png image; - * @constructor - */ - public async TakeScreenshot(): Promise - public async TakeScreenshot(format: "image"): Promise - public async TakeScreenshot(format: "blob"): Promise - public async TakeScreenshot(format: "image" | "blob"): Promise - public async TakeScreenshot(format: "image" | "blob" = "image"): Promise { - console.log("Taking a screenshot...") - const screenshotter = new SimpleMapScreenshoter() - screenshotter.addTo(this.leafletMap.data) - const result = await screenshotter.takeScreen(format ?? "image") - if (format === "image" && typeof result === "string") { - return result - } - if (format === "blob" && result instanceof Blob) { - return result - } - throw "Something went wrong while creating the screenshot: " + result - } - - protected InnerConstructElement(): HTMLElement { - const div = document.createElement("div") - div.id = this._id - div.style.height = "100%" - div.style.width = "100%" - div.style.minWidth = "40px" - div.style.minHeight = "40px" - div.style.position = "relative" - const wrapper = document.createElement("div") - wrapper.appendChild(div) - const self = this - // @ts-ignore - const resizeObserver = new ResizeObserver((_) => { - if (wrapper.clientHeight === 0 || wrapper.clientWidth === 0) { - return - } - if ( - wrapper.offsetParent === null || - window.getComputedStyle(wrapper).display === "none" - ) { - // Not visible - return - } - try { - self.InitMap() - } catch (e) { - console.debug("Could not construct a minimap:", e) - } - - try { - self.leafletMap?.data?.invalidateSize() - } catch (e) { - console.debug("Could not invalidate size of a minimap:", e) - } - }) - - resizeObserver.observe(div) - - if (this._addLayerControl) { - const switcher = new BackgroundMapSwitch( - { - locationControl: this.location, - backgroundLayer: this._background, - }, - this._background - ).SetClass("top-0 right-0 z-above-map absolute") - wrapper.appendChild(switcher.ConstructElement()) - } - - return wrapper - } - - private InitMap() { - if (this._constructedHtmlElement === undefined) { - // This element isn't initialized yet - return - } - - if (document.getElementById(this._id) === null) { - // not yet attached, we probably got some other event - return - } - - if (this._isInited) { - return - } - this._isInited = true - const location = this.location - const self = this - let currentLayer = this._background.data.layer() - let latLon = <[number, number]>[location.data?.lat ?? 0, location.data?.lon ?? 0] - if (isNaN(latLon[0]) || isNaN(latLon[1])) { - latLon = [0, 0] - } - const options = { - center: latLon, - zoom: location.data?.zoom ?? 2, - layers: [currentLayer], - zoomControl: false, - attributionControl: this._attribution !== undefined, - dragging: this._allowMoving, - scrollWheelZoom: this._allowMoving, - doubleClickZoom: this._allowMoving, - keyboard: this._allowMoving, - touchZoom: this._allowMoving, - // Disabling this breaks the geojson layer - don't ask me why! zoomAnimation: this._allowMoving, - fadeAnimation: this._allowMoving, - maxZoom: 21, - } - - Utils.Merge(this._leafletoptions, options) - /* - * Somehow, the element gets '_leaflet_id' set on chrome. - * When attempting to init this leaflet map, it'll throw an exception and the map won't show up. - * Simply removing '_leaflet_id' fixes the issue. - * See https://github.com/pietervdvn/MapComplete/issues/726 - * */ - delete document.getElementById(this._id)["_leaflet_id"] - - const map = L.map(this._id, options) - if (self._onFullyLoaded !== undefined) { - currentLayer.on("load", () => { - console.log("Fully loaded all tiles!") - self._onFullyLoaded(map) - }) - } - - // Users are not allowed to zoom to the 'copies' on the left and the right, stuff goes wrong then - // We give a bit of leeway for people on the edges - // Also see: https://www.reddit.com/r/openstreetmap/comments/ih4zzc/mapcomplete_a_new_easytouse_editor/g31ubyv/ - - map.setMaxBounds([ - [-100, -200], - [100, 200], - ]) - - if (this._attribution !== undefined) { - if (this._attribution === true) { - map.attributionControl.setPrefix(false) - } else { - map.attributionControl.setPrefix("") - } - } - - this._background.addCallbackAndRun((layer) => { - const newLayer = layer.layer() - if (currentLayer !== undefined) { - map.removeLayer(currentLayer) - } - currentLayer = newLayer - if (self._onFullyLoaded !== undefined) { - currentLayer.on("load", () => { - console.log("Fully loaded all tiles!") - self._onFullyLoaded(map) - }) - } - map.addLayer(newLayer) - if (self._attribution !== true && self._attribution !== false) { - self._attribution?.AttachTo("leaflet-attribution") - } - }) - - let isRecursing = false - map.on("moveend", function () { - if (isRecursing) { - return - } - if ( - map.getZoom() === location.data.zoom && - map.getCenter().lat === location.data.lat && - map.getCenter().lng === location.data.lon - ) { - return - } - location.data.zoom = map.getZoom() - location.data.lat = map.getCenter().lat - location.data.lon = map.getCenter().lng - isRecursing = true - location.ping() - - if (self.bounds !== undefined) { - self.bounds.setData(BBox.fromLeafletBounds(map.getBounds())) - } - - isRecursing = false // This is ugly, I know - }) - - location.addCallback((loc) => { - const mapLoc = map.getCenter() - const dlat = Math.abs(loc.lat - mapLoc[0]) - const dlon = Math.abs(loc.lon - mapLoc[1]) - - if (dlat < 0.000001 && dlon < 0.000001 && map.getZoom() === loc.zoom) { - return - } - map.setView([loc.lat, loc.lon], loc.zoom) - }) - - if (self.bounds !== undefined) { - self.bounds.setData(BBox.fromLeafletBounds(map.getBounds())) - } - - if (this._options.lastClickLocation) { - const lastClickLocation = this._options.lastClickLocation - map.addEventListener("click", function (e: LeafletMouseEvent) { - if (e.originalEvent["dismissed"]) { - return - } - lastClickLocation?.setData({ lat: e.latlng.lat, lon: e.latlng.lng }) - }) - - map.on("contextmenu", function (e) { - // @ts-ignore - lastClickLocation?.setData({ lat: e.latlng.lat, lon: e.latlng.lng }) - map.setZoom(map.getZoom() + 1) - }) - } - - this.leafletMap.setData(map) - } -} diff --git a/UI/Base/SvelteUIElement.ts b/UI/Base/SvelteUIElement.ts index 67115f3c40..68a51304a3 100644 --- a/UI/Base/SvelteUIElement.ts +++ b/UI/Base/SvelteUIElement.ts @@ -4,6 +4,7 @@ import { SvelteComponentTyped } from "svelte" /** * The SvelteUIComponent serves as a translating class which which wraps a SvelteElement into the BaseUIElement framework. + * Also see ToSvelte.svelte for the opposite conversion */ export default class SvelteUIElement< Props extends Record = any, diff --git a/UI/BigComponents/AddNewMarker.ts b/UI/BigComponents/AddNewMarker.ts index 6c3fe93d6e..c260846fd7 100644 --- a/UI/BigComponents/AddNewMarker.ts +++ b/UI/BigComponents/AddNewMarker.ts @@ -1,4 +1,4 @@ -import { UIEventSource } from "../../Logic/UIEventSource" +import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"; import Combine from "../Base/Combine" import Translations from "../i18n/Translations" import { VariableUiElement } from "../Base/VariableUIElement" @@ -24,13 +24,13 @@ export default class AddNewMarker extends Combine { for (const preset of filteredLayer.layerDef.presets) { const tags = TagUtils.KVtoProperties(preset.tags) const icon = layer.mapRendering[0] - .GenerateLeafletStyle(new UIEventSource(tags), false) + .RenderIcon(new ImmutableStore(tags), false) .html.SetClass("block relative") .SetStyle("width: 42px; height: 42px;") icons.push(icon) if (last === undefined) { last = layer.mapRendering[0] - .GenerateLeafletStyle(new UIEventSource(tags), false) + .RenderIcon(new ImmutableStore(tags), false) .html.SetClass("block relative") .SetStyle("width: 42px; height: 42px;") } diff --git a/UI/BigComponents/Attribution.ts b/UI/BigComponents/Attribution.ts deleted file mode 100644 index d1f7bb4973..0000000000 --- a/UI/BigComponents/Attribution.ts +++ /dev/null @@ -1,92 +0,0 @@ -import Link from "../Base/Link" -import Svg from "../../Svg" -import Combine from "../Base/Combine" -import { UIEventSource } from "../../Logic/UIEventSource" -import UserDetails from "../../Logic/Osm/OsmConnection" -import Constants from "../../Models/Constants" -import Loc from "../../Models/Loc" -import { VariableUiElement } from "../Base/VariableUIElement" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import { BBox } from "../../Logic/BBox" -import { Utils } from "../../Utils" -import Translations from "../i18n/Translations" - -/** - * The bottom right attribution panel in the leaflet map - */ -export default class Attribution extends Combine { - constructor( - location: UIEventSource, - userDetails: UIEventSource, - layoutToUse: LayoutConfig, - currentBounds: UIEventSource - ) { - const mapComplete = new Link( - `Mapcomplete ${Constants.vNumber}`, - "https://github.com/pietervdvn/MapComplete", - true - ) - const reportBug = new Link( - Svg.bug_ui().SetClass("small-image"), - "https://github.com/pietervdvn/MapComplete/issues", - true - ) - - const layoutId = layoutToUse?.id - const stats = new Link( - Svg.statistics_ui().SetClass("small-image"), - Utils.OsmChaLinkFor(31, layoutId), - true - ) - - const idLink = location.map( - (location) => - `https://www.openstreetmap.org/edit?editor=id#map=${location?.zoom ?? 0}/${ - location?.lat ?? 0 - }/${location?.lon ?? 0}` - ) - const editHere = new Link(Svg.pencil_ui().SetClass("small-image"), idLink, true) - - const mapillaryLink = location.map( - (location) => - `https://www.mapillary.com/app/?focus=map&lat=${location?.lat ?? 0}&lng=${ - location?.lon ?? 0 - }&z=${Math.max((location?.zoom ?? 2) - 1, 1)}` - ) - const mapillary = new Link( - Svg.mapillary_black_ui().SetClass("small-image"), - mapillaryLink, - true - ) - - const mapDataByOsm = new Link( - Translations.t.general.attribution.mapDataByOsm, - "https://openstreetmap.org/copyright", - true - ) - - const editWithJosm = new VariableUiElement( - userDetails.map( - (userDetails) => { - if (userDetails.csCount < Constants.userJourney.tagsVisibleAndWikiLinked) { - return undefined - } - const bounds: any = currentBounds.data - if (bounds === undefined) { - return undefined - } - const top = bounds.getNorth() - const bottom = bounds.getSouth() - const right = bounds.getEast() - const left = bounds.getWest() - - const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}` - return new Link(Svg.josm_logo_ui().SetClass("small-image"), josmLink, true) - }, - [location, currentBounds] - ) - ) - super([mapComplete, reportBug, stats, editHere, editWithJosm, mapillary, mapDataByOsm]) - this.SetClass("flex") - } -} diff --git a/UI/BigComponents/GeolocationControl.ts b/UI/BigComponents/GeolocationControl.ts index 9350d252ae..ee5465b12d 100644 --- a/UI/BigComponents/GeolocationControl.ts +++ b/UI/BigComponents/GeolocationControl.ts @@ -1,25 +1,19 @@ import { VariableUiElement } from "../Base/VariableUIElement" import Svg from "../../Svg" -import { UIEventSource } from "../../Logic/UIEventSource" +import { Store, UIEventSource } from "../../Logic/UIEventSource" import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler" import { BBox } from "../../Logic/BBox" -import Loc from "../../Models/Loc" import Hotkeys from "../Base/Hotkeys" import Translations from "../i18n/Translations" import Constants from "../../Models/Constants" +import { MapProperties } from "../../Models/MapProperties" /** * Displays an icon depending on the state of the geolocation. * Will set the 'lock' if clicked twice */ export class GeolocationControl extends VariableUiElement { - constructor( - geolocationHandler: GeoLocationHandler, - state: { - locationControl: UIEventSource - currentBounds: UIEventSource - } - ) { + constructor(geolocationHandler: GeoLocationHandler, state: MapProperties) { const lastClick = new UIEventSource(undefined) lastClick.addCallbackD((date) => { geolocationHandler.geolocationState.requestMoment.setData(date) @@ -48,7 +42,7 @@ export class GeolocationControl extends VariableUiElement { if (permission === "denied") { return Svg.location_refused_svg() } - if (geolocationState.isLocked.data) { + if (!geolocationState.allowMoving.data) { return Svg.location_locked_svg() } @@ -77,7 +71,7 @@ export class GeolocationControl extends VariableUiElement { }, [ geolocationState.currentGPSLocation, - geolocationState.isLocked, + geolocationState.allowMoving, geolocationHandler.mapHasMoved, lastClickWithinThreeSecs, lastRequestWithinTimeout, @@ -95,9 +89,9 @@ export class GeolocationControl extends VariableUiElement { await geolocationState.requestPermission() } - if (geolocationState.isLocked.data === true) { + if (geolocationState.allowMoving.data === false) { // Unlock - geolocationState.isLocked.setData(false) + geolocationState.allowMoving.setData(true) return } @@ -109,21 +103,17 @@ export class GeolocationControl extends VariableUiElement { // A location _is_ known! Let's move to this location const currentLocation = geolocationState.currentGPSLocation.data - const inBounds = state.currentBounds.data.contains([ + const inBounds = state.bounds.data.contains([ currentLocation.longitude, currentLocation.latitude, ]) geolocationHandler.MoveMapToCurrentLocation() if (inBounds) { - const lc = state.locationControl.data - state.locationControl.setData({ - ...lc, - zoom: lc.zoom + 3, - }) + state.zoom.update((z) => z + 3) } if (lastClickWithinThreeSecs.data) { - geolocationState.isLocked.setData(true) + geolocationState.allowMoving.setData(false) lastClick.setData(undefined) return } diff --git a/UI/BigComponents/LeftControls.ts b/UI/BigComponents/LeftControls.ts index 9a342c0baa..7d37489e0f 100644 --- a/UI/BigComponents/LeftControls.ts +++ b/UI/BigComponents/LeftControls.ts @@ -11,7 +11,6 @@ import BackgroundMapSwitch from "./BackgroundMapSwitch" import Lazy from "../Base/Lazy" import { VariableUiElement } from "../Base/VariableUIElement" import FeatureInfoBox from "../Popup/FeatureInfoBox" -import CopyrightPanel from "./CopyrightPanel" import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import Hotkeys from "../Base/Hotkeys" import { DefaultGuiState } from "../DefaultGuiState" @@ -21,7 +20,7 @@ export default class LeftControls extends Combine { const currentViewFL = state.currentView?.layer const currentViewAction = new Toggle( new Lazy(() => { - const feature: Store = state.currentView.features.map((ffs) => ffs[0]?.feature) + const feature: Store = state.currentView.features.map((ffs) => ffs[0]) const icon = new VariableUiElement( feature.map((feature) => { const defaultIcon = Svg.checkbox_empty_svg() diff --git a/UI/BigComponents/LevelSelector.ts b/UI/BigComponents/LevelSelector.ts index 789380db99..d40020f259 100644 --- a/UI/BigComponents/LevelSelector.ts +++ b/UI/BigComponents/LevelSelector.ts @@ -1,5 +1,5 @@ import FloorLevelInputElement from "../Input/FloorLevelInputElement" -import MapState, { GlobalFilter } from "../../Logic/State/MapState" +import MapState from "../../Logic/State/MapState" import { TagsFilter } from "../../Logic/Tags/TagsFilter" import { RegexTag } from "../../Logic/Tags/RegexTag" import { Or } from "../../Logic/Tags/Or" @@ -11,6 +11,7 @@ import { BBox } from "../../Logic/BBox" import { TagUtils } from "../../Logic/Tags/TagUtils" import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" import { Store } from "../../Logic/UIEventSource" +import { GlobalFilter } from "../../Logic/State/GlobalFilter" /*** * The element responsible for the level input element and picking the right level, showing and hiding at the right time, ... diff --git a/UI/BigComponents/RightControls.ts b/UI/BigComponents/RightControls.ts index 189559ba54..c34daa4797 100644 --- a/UI/BigComponents/RightControls.ts +++ b/UI/BigComponents/RightControls.ts @@ -9,30 +9,9 @@ import LevelSelector from "./LevelSelector" import { GeolocationControl } from "./GeolocationControl" export default class RightControls extends Combine { - constructor( - state: MapState & { featurePipeline: FeaturePipeline }, - geolocationHandler: GeoLocationHandler - ) { - const geolocationButton = Toggle.If(state.featureSwitchGeolocation, () => - new MapControlButton(new GeolocationControl(geolocationHandler, state), { - dontStyle: true, - }).SetClass("p-1") - ) - - const plus = new MapControlButton(Svg.plus_svg()).onClick(() => { - state.locationControl.data.zoom++ - state.locationControl.ping() - }) - - const min = new MapControlButton(Svg.min_svg()).onClick(() => { - state.locationControl.data.zoom-- - state.locationControl.ping() - }) - + constructor(state: MapState & { featurePipeline: FeaturePipeline }) { const levelSelector = new LevelSelector(state) - super( - [levelSelector, plus, min, geolocationButton].map((el) => el.SetClass("m-0.5 md:m-1")) - ) + super([levelSelector].map((el) => el.SetClass("m-0.5 md:m-1"))) this.SetClass("flex flex-col items-center") } } diff --git a/UI/BigComponents/SearchAndGo.ts b/UI/BigComponents/SearchAndGo.ts index 56290896d8..f6063c8606 100644 --- a/UI/BigComponents/SearchAndGo.ts +++ b/UI/BigComponents/SearchAndGo.ts @@ -1,4 +1,4 @@ -import { UIEventSource } from "../../Logic/UIEventSource" +import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Translation } from "../i18n/Translation" import Svg from "../../Svg" import { TextField } from "../Input/TextField" @@ -7,10 +7,15 @@ import Translations from "../i18n/Translations" import Hash from "../../Logic/Web/Hash" import Combine from "../Base/Combine" import Locale from "../i18n/Locale" +import { BBox } from "../../Logic/BBox" export default class SearchAndGo extends Combine { private readonly _searchField: TextField - constructor(state: { leafletMap: UIEventSource; selectedElement?: UIEventSource }) { + constructor(state: { + leafletMap: UIEventSource + selectedElement?: UIEventSource + bounds?: Store + }) { const goButton = Svg.search_ui().SetClass("w-8 h-8 full-rounded border-black float-right") const placeholder = new UIEventSource(Translations.t.general.search.search) @@ -49,7 +54,7 @@ export default class SearchAndGo extends Combine { searchField.GetValue().setData("") placeholder.setData(Translations.t.general.search.searching) try { - const result = await Geocoding.Search(searchString) + const result = await Geocoding.Search(searchString, state.bounds.data) console.log("Search result", result) if (result.length == 0) { diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts index 2cebb2512e..3582848ae0 100644 --- a/UI/BigComponents/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -1,7 +1,7 @@ /** * Asks to add a feature at the last clicked location, at least if zoom is sufficient */ -import { Store, UIEventSource } from "../../Logic/UIEventSource" +import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" import Svg from "../../Svg" import { SubtleButton } from "../Base/SubtleButton" import Combine from "../Base/Combine" @@ -22,13 +22,12 @@ import { Changes } from "../../Logic/Osm/Changes" import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" import { ElementStorage } from "../../Logic/ElementStorage" import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint" -import BaseLayer from "../../Models/BaseLayer" import Loading from "../Base/Loading" import Hash from "../../Logic/Web/Hash" -import { GlobalFilter } from "../../Logic/State/MapState" import { WayId } from "../../Models/OsmFeature" import { Tag } from "../../Logic/Tags/Tag" import { LoginToggle } from "../Popup/LoginButton" +import { GlobalFilter } from "../../Models/GlobalFilter" /* * The SimpleAddUI is a single panel, which can have multiple states: @@ -288,7 +287,7 @@ export default class SimpleAddUI extends LoginToggle { const tags = TagUtils.KVtoProperties(preset.tags ?? []) let icon: () => BaseUIElement = () => layer.layerDef.mapRendering[0] - .GenerateLeafletStyle(new UIEventSource(tags), false) + .RenderIcon(new ImmutableStore(tags), false) .html.SetClass("w-12 h-12 block relative") const presetInfo: PresetInfo = { layerToAddTo: layer, diff --git a/UI/DashboardGui.ts b/UI/DashboardGui.ts deleted file mode 100644 index 213e692bf7..0000000000 --- a/UI/DashboardGui.ts +++ /dev/null @@ -1,305 +0,0 @@ -import FeaturePipelineState from "../Logic/State/FeaturePipelineState" -import { DefaultGuiState } from "./DefaultGuiState" -import { FixedUiElement } from "./Base/FixedUiElement" -import { Utils } from "../Utils" -import Combine from "./Base/Combine" -import ShowDataLayer from "./ShowDataLayer/ShowDataLayer" -import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import home_location_json from "../assets/layers/home_location/home_location.json" -import State from "../State" -import Title from "./Base/Title" -import { MinimapObj } from "./Base/Minimap" -import BaseUIElement from "./BaseUIElement" -import { VariableUiElement } from "./Base/VariableUIElement" -import { GeoOperations } from "../Logic/GeoOperations" -import { OsmFeature } from "../Models/OsmFeature" -import SearchAndGo from "./BigComponents/SearchAndGo" -import FeatureInfoBox from "./Popup/FeatureInfoBox" -import { UIEventSource } from "../Logic/UIEventSource" -import LanguagePicker from "./LanguagePicker" -import Lazy from "./Base/Lazy" -import TagRenderingAnswer from "./Popup/TagRenderingAnswer" -import Hash from "../Logic/Web/Hash" -import FilterView from "./BigComponents/FilterView" -import Translations from "./i18n/Translations" -import Constants from "../Models/Constants" -import SimpleAddUI from "./BigComponents/SimpleAddUI" -import BackToIndex from "./BigComponents/BackToIndex" -import StatisticsPanel from "./BigComponents/StatisticsPanel" - -export default class DashboardGui { - private readonly state: FeaturePipelineState - private readonly currentView: UIEventSource<{ - title: string | BaseUIElement - contents: string | BaseUIElement - }> = new UIEventSource(undefined) - - constructor(state: FeaturePipelineState, guiState: DefaultGuiState) { - this.state = state - } - - private viewSelector( - shown: BaseUIElement, - title: string | BaseUIElement, - contents: string | BaseUIElement, - hash?: string - ): BaseUIElement { - const currentView = this.currentView - const v = { title, contents } - shown.SetClass("pl-1 pr-1 rounded-md") - shown.onClick(() => { - currentView.setData(v) - }) - Hash.hash.addCallbackAndRunD((h) => { - if (h === hash) { - currentView.setData(v) - } - }) - currentView.addCallbackAndRunD((cv) => { - if (cv == v) { - shown.SetClass("bg-unsubtle") - Hash.hash.setData(hash) - } else { - shown.RemoveClass("bg-unsubtle") - } - }) - return shown - } - - private singleElementCache: Record = {} - - private singleElementView( - element: OsmFeature, - layer: LayerConfig, - distance: number - ): BaseUIElement { - if (this.singleElementCache[element.properties.id] !== undefined) { - return this.singleElementCache[element.properties.id] - } - const tags = this.state.allElements.getEventSourceById(element.properties.id) - const title = new Combine([ - new Title(new TagRenderingAnswer(tags, layer.title, this.state), 4), - distance < 900 - ? Math.floor(distance) + "m away" - : Utils.Round(distance / 1000) + "km away", - ]).SetClass("flex justify-between") - - return (this.singleElementCache[element.properties.id] = this.viewSelector( - title, - new Lazy(() => FeatureInfoBox.GenerateTitleBar(tags, layer, this.state)), - new Lazy(() => FeatureInfoBox.GenerateContent(tags, layer, this.state)) - // element.properties.id - )) - } - - private mainElementsView( - elements: { element: OsmFeature; layer: LayerConfig; distance: number }[] - ): BaseUIElement { - const self = this - if (elements === undefined) { - return new FixedUiElement("Initializing") - } - if (elements.length == 0) { - return new FixedUiElement("No elements in view") - } - return new Combine( - elements.map((e) => self.singleElementView(e.element, e.layer, e.distance)) - ) - } - - private documentationButtonFor(layerConfig: LayerConfig): BaseUIElement { - return this.viewSelector( - Translations.W(layerConfig.name?.Clone() ?? layerConfig.id), - new Combine(["Documentation about ", layerConfig.name?.Clone() ?? layerConfig.id]), - layerConfig.GenerateDocumentation([]), - "documentation-" + layerConfig.id - ) - } - - private allDocumentationButtons(): BaseUIElement { - const layers = this.state.layoutToUse.layers - .filter((l) => Constants.priviliged_layers.indexOf(l.id) < 0) - .filter((l) => !l.id.startsWith("note_import_")) - - if (layers.length === 1) { - return this.documentationButtonFor(layers[0]) - } - return this.viewSelector( - new FixedUiElement("Documentation"), - "Documentation", - new Combine(layers.map((l) => this.documentationButtonFor(l).SetClass("flex flex-col"))) - ) - } - - public setup(): void { - const state = this.state - - if (this.state.layoutToUse.customCss !== undefined) { - if (window.location.pathname.indexOf("index") >= 0) { - Utils.LoadCustomCss(this.state.layoutToUse.customCss) - } - } - const map = this.SetupMap() - - Utils.downloadJson("./service-worker-version") - .then((data) => console.log("Service worker", data)) - .catch((_) => console.log("Service worker not active")) - - document.getElementById("centermessage").classList.add("hidden") - - const layers: Record = {} - for (const layer of state.layoutToUse.layers) { - layers[layer.id] = layer - } - - const self = this - const elementsInview = new UIEventSource< - { - distance: number - center: [number, number] - element: OsmFeature - layer: LayerConfig - }[] - >([]) - - function update() { - const mapCenter = <[number, number]>[ - self.state.locationControl.data.lon, - self.state.locationControl.data.lon, - ] - const elements = self.state.featurePipeline - .getAllVisibleElementsWithmeta(self.state.currentBounds.data) - .map((el) => { - const distance = GeoOperations.distanceBetween(el.center, mapCenter) - return { ...el, distance } - }) - elements.sort((e0, e1) => e0.distance - e1.distance) - elementsInview.setData(elements) - } - - map.bounds.addCallbackAndRun(update) - state.featurePipeline.newDataLoadedSignal.addCallback(update) - state.filteredLayers.addCallbackAndRun((fls) => { - for (const fl of fls) { - fl.isDisplayed.addCallback(update) - fl.appliedFilters.addCallback(update) - } - }) - - const filterView = new Lazy(() => { - return new FilterView(state.filteredLayers, state.overlayToggles, state) - }) - const welcome = new Combine([ - state.layoutToUse.description, - state.layoutToUse.descriptionTail, - ]) - self.currentView.setData({ title: state.layoutToUse.title, contents: welcome }) - const filterViewIsOpened = new UIEventSource(false) - filterViewIsOpened.addCallback((_) => - self.currentView.setData({ title: "filters", contents: filterView }) - ) - - const newPointIsShown = new UIEventSource(false) - const addNewPoint = new SimpleAddUI( - new UIEventSource(true), - new UIEventSource(undefined), - filterViewIsOpened, - state, - state.locationControl - ) - const addNewPointTitle = "Add a missing point" - this.currentView.addCallbackAndRunD((cv) => { - newPointIsShown.setData(cv.contents === addNewPoint) - }) - newPointIsShown.addCallbackAndRun((isShown) => { - if (isShown) { - if (self.currentView.data.contents !== addNewPoint) { - self.currentView.setData({ title: addNewPointTitle, contents: addNewPoint }) - } - } else { - if (self.currentView.data.contents === addNewPoint) { - self.currentView.setData(undefined) - } - } - }) - - new Combine([ - new Combine([ - this.viewSelector( - new Title(state.layoutToUse.title.Clone(), 2), - state.layoutToUse.title.Clone(), - welcome, - "welcome" - ), - map.SetClass("w-full h-64 shrink-0 rounded-lg"), - new SearchAndGo(state), - this.viewSelector( - new Title( - new VariableUiElement( - elementsInview.map( - (elements) => "There are " + elements?.length + " elements in view" - ) - ) - ), - "Statistics", - new StatisticsPanel(elementsInview, this.state), - "statistics" - ), - - this.viewSelector(new FixedUiElement("Filter"), "Filters", filterView, "filters"), - this.viewSelector( - new Combine(["Add a missing point"]), - addNewPointTitle, - addNewPoint - ), - - new VariableUiElement( - elementsInview.map((elements) => - this.mainElementsView(elements).SetClass("block m-2") - ) - ).SetClass( - "block shrink-2 overflow-x-auto h-full border-2 border-subtle rounded-lg" - ), - this.allDocumentationButtons(), - new LanguagePicker(Object.keys(state.layoutToUse.title.translations)).SetClass( - "mt-2" - ), - new BackToIndex(), - ]).SetClass("w-1/2 lg:w-1/4 m-4 flex flex-col shrink-0 grow-0"), - new VariableUiElement( - this.currentView.map(({ title, contents }) => { - return new Combine([ - new Title(Translations.W(title), 2).SetClass( - "shrink-0 border-b-4 border-subtle" - ), - Translations.W(contents).SetClass("shrink-2 overflow-y-auto block"), - ]).SetClass("flex flex-col h-full") - }) - ).SetClass( - "w-1/2 lg:w-3/4 m-4 p-2 border-2 border-subtle rounded-xl m-4 ml-0 mr-8 shrink-0 grow-0" - ), - ]) - .SetClass("flex h-full") - .AttachTo("leafletDiv") - } - - private SetupMap(): MinimapObj & BaseUIElement { - const state = this.state - - new ShowDataLayer({ - leafletMap: state.leafletMap, - layerToShow: new LayerConfig(home_location_json, "home_location", true), - features: state.homeLocation, - state, - }) - - state.leafletMap.addCallbackAndRunD((_) => { - // Lets assume that all showDataLayers are initialized at this point - state.selectedElement.ping() - State.state.locationControl.ping() - return true - }) - - return state.mainMapObject - } -} diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index f2e3c8a4cb..c793c7a42b 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -171,9 +171,6 @@ export default class DefaultGUI { const state = this.state const guiState = this.guiState - // Attach the map - state.mainMapObject.SetClass("w-full h-full").AttachTo("leafletDiv") - this.setupClickDialogOnMap(guiState.filterViewIsOpened, state) new ShowDataLayer({ diff --git a/UI/ImportFlow/ConflationChecker.ts b/UI/ImportFlow/ConflationChecker.ts index 5ec1de4434..70ba96c4de 100644 --- a/UI/ImportFlow/ConflationChecker.ts +++ b/UI/ImportFlow/ConflationChecker.ts @@ -138,12 +138,6 @@ export default class ConflationChecker location, background, bounds: currentBounds, - attribution: new Attribution( - location, - state.osmConnection.userDetails, - undefined, - currentBounds - ), }) osmLiveData.SetClass("w-full").SetStyle("height: 500px") diff --git a/UI/ImportFlow/MapPreview.ts b/UI/ImportFlow/MapPreview.ts index 9fb71489de..c000c7a68a 100644 --- a/UI/ImportFlow/MapPreview.ts +++ b/UI/ImportFlow/MapPreview.ts @@ -9,10 +9,6 @@ import { DropDown } from "../Input/DropDown" import { Utils } from "../../Utils" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import Loc from "../../Models/Loc" -import Minimap from "../Base/Minimap" -import Attribution from "../BigComponents/Attribution" -import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer" -import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import Toggle from "../Input/Toggle" import { VariableUiElement } from "../Base/VariableUIElement" @@ -21,12 +17,14 @@ import { FlowStep } from "./FlowStep" import ScrollableFullScreen from "../Base/ScrollableFullScreen" import Title from "../Base/Title" import CheckBoxes from "../Input/Checkboxes" -import AllTagsPanel from "../AllTagsPanel.svelte" +import AllTagsPanel from "../Popup/AllTagsPanel.svelte" import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch" import { Feature, Point } from "geojson" import DivContainer from "../Base/DivContainer" -import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" import SvelteUIElement from "../Base/SvelteUIElement" +import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers" +import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" +import ShowDataLayer from "../Map/ShowDataLayer" class PreviewPanel extends ScrollableFullScreen { constructor(tags: UIEventSource) { @@ -110,21 +108,11 @@ export class MapPreview return matching }) - const background = new UIEventSource(AvailableBaseLayers.osmCarto) + const background = new UIEventSource(AvailableRasterLayers.osmCarto) const location = new UIEventSource({ lat: 0, lon: 0, zoom: 1 }) const currentBounds = new UIEventSource(undefined) - const map = Minimap.createMiniMap({ - allowMoving: true, - location, - background, - bounds: currentBounds, - attribution: new Attribution( - location, - state.osmConnection.userDetails, - undefined, - currentBounds - ), - }) + const { ui, mapproperties, map } = MapLibreAdaptor.construct() + const layerControl = new BackgroundMapSwitch( { backgroundLayer: background, @@ -132,15 +120,14 @@ export class MapPreview }, background ) - map.SetClass("w-full").SetStyle("height: 500px") + ui.SetClass("w-full").SetStyle("height: 500px") layerPicker.GetValue().addCallbackAndRunD((layerToShow) => { - new ShowDataLayer({ - layerToShow, + new ShowDataLayer(map, { + layer: layerToShow, zoomToFeatures: true, features: new StaticFeatureSource(matching), - leafletMap: map.leafletMap, - popup: (tag) => new PreviewPanel(tag), + buildPopup: (tag) => new PreviewPanel(tag), }) }) @@ -171,9 +158,8 @@ export class MapPreview new Title(t.title, 1), layerPicker, new Toggle(t.autodetected.SetClass("thanks"), undefined, autodetected), - mismatchIndicator, - map, + ui, new DivContainer("fullscreen"), layerControl, confirm, diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index 682ab9f70e..3b9aba3ac3 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -1,34 +1,56 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" import type { Map as MLMap } from "maplibre-gl" +import { Map as MlMap } from "maplibre-gl" import { RasterLayerPolygon, RasterLayerProperties } from "../../Models/RasterLayers" import { Utils } from "../../Utils" import { BBox } from "../../Logic/BBox" +import { MapProperties } from "../../Models/MapProperties" +import SvelteUIElement from "../Base/SvelteUIElement" +import MaplibreMap from "./MaplibreMap.svelte" +import Constants from "../../Models/Constants" -export interface MapState { +/** + * The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties` + */ +export class MapLibreAdaptor implements MapProperties { + private static maplibre_control_handlers = [ + "scrollZoom", + "boxZoom", + "dragRotate", + "dragPan", + "keyboard", + "doubleClickZoom", + "touchZoomRotate", + ] readonly location: UIEventSource<{ lon: number; lat: number }> readonly zoom: UIEventSource readonly bounds: Store readonly rasterLayer: UIEventSource -} -export class MapLibreAdaptor implements MapState { + readonly maxbounds: UIEventSource + readonly allowMoving: UIEventSource private readonly _maplibreMap: Store - - readonly location: UIEventSource<{ lon: number; lat: number }> - readonly zoom: UIEventSource - readonly bounds: Store - readonly rasterLayer: UIEventSource private readonly _bounds: UIEventSource - /** * Used for internal bookkeeping (to remove a rasterLayer when done loading) * @private */ private _currentRasterLayer: string - constructor(maplibreMap: Store, state?: Partial>) { + + constructor(maplibreMap: Store, state?: Partial>) { this._maplibreMap = maplibreMap this.location = state?.location ?? new UIEventSource({ lon: 0, lat: 0 }) this.zoom = state?.zoom ?? new UIEventSource(1) + this.zoom.addCallbackAndRunD((z) => { + if (z < 0) { + this.zoom.setData(0) + } + if (z > 24) { + this.zoom.setData(24) + } + }) + this.maxbounds = state?.maxbounds ?? new UIEventSource(undefined) + this.allowMoving = state?.allowMoving ?? new UIEventSource(true) this._bounds = new UIEventSource(BBox.global) this.bounds = this._bounds this.rasterLayer = @@ -38,20 +60,26 @@ export class MapLibreAdaptor implements MapState { maplibreMap.addCallbackAndRunD((map) => { map.on("load", () => { self.setBackground() + self.MoveMapToCurrentLoc(self.location.data) + self.SetZoom(self.zoom.data) + self.setMaxBounds(self.maxbounds.data) + self.setAllowMoving(self.allowMoving.data) }) - self.MoveMapToCurrentLoc(this.location.data) - self.SetZoom(this.zoom.data) + self.MoveMapToCurrentLoc(self.location.data) + self.SetZoom(self.zoom.data) + self.setMaxBounds(self.maxbounds.data) + self.setAllowMoving(self.allowMoving.data) map.on("moveend", () => { const dt = this.location.data dt.lon = map.getCenter().lng dt.lat = map.getCenter().lat this.location.ping() - this.zoom.setData(map.getZoom()) + this.zoom.setData(Math.round(map.getZoom() * 10) / 10) }) }) this.rasterLayer.addCallback((_) => - self.setBackground().catch((e) => { + self.setBackground().catch((_) => { console.error("Could not set background") }) ) @@ -60,25 +88,25 @@ export class MapLibreAdaptor implements MapState { self.MoveMapToCurrentLoc(loc) }) this.zoom.addCallbackAndRunD((z) => self.SetZoom(z)) + this.maxbounds.addCallbackAndRun((bbox) => self.setMaxBounds(bbox)) + this.allowMoving.addCallbackAndRun((allowMoving) => self.setAllowMoving(allowMoving)) } - private SetZoom(z: number) { - const map = this._maplibreMap.data - if (map === undefined || z === undefined) { - return - } - if (map.getZoom() !== z) { - map.setZoom(z) - } - } - private MoveMapToCurrentLoc(loc: { lat: number; lon: number }) { - const map = this._maplibreMap.data - if (map === undefined || loc === undefined) { - return - } - const center = map.getCenter() - if (center.lng !== loc.lon || center.lat !== loc.lat) { - map.setCenter({ lng: loc.lon, lat: loc.lat }) + /** + * Convenience constructor + */ + public static construct(): { + map: Store + ui: SvelteUIElement + mapproperties: MapProperties + } { + const mlmap = new UIEventSource(undefined) + return { + map: mlmap, + ui: new SvelteUIElement(MaplibreMap, { + map: mlmap, + }), + mapproperties: new MapLibreAdaptor(mlmap), } } @@ -103,7 +131,6 @@ export class MapLibreAdaptor implements MapState { const subdomains = url.match(/\{switch:([a-zA-Z0-9,]*)}/) if (subdomains !== null) { - console.log("Found a switch:", subdomains) const options = subdomains[1].split(",") const option = options[Math.floor(Math.random() * options.length)] url = url.replace(subdomains[0], option) @@ -112,6 +139,28 @@ export class MapLibreAdaptor implements MapState { return url } + private SetZoom(z: number) { + const map = this._maplibreMap.data + if (!map || z === undefined) { + return + } + if (Math.abs(map.getZoom() - z) > 0.01) { + map.setZoom(z) + } + } + + private MoveMapToCurrentLoc(loc: { lat: number; lon: number }) { + const map = this._maplibreMap.data + if (!map || loc === undefined) { + return + } + + const center = map.getCenter() + if (center.lng !== loc.lon || center.lat !== loc.lat) { + map.setCenter({ lng: loc.lon, lat: loc.lat }) + } + } + private async awaitStyleIsLoaded(): Promise { const map = this._maplibreMap.data if (map === undefined) { @@ -125,7 +174,6 @@ export class MapLibreAdaptor implements MapState { private removeCurrentLayer(map: MLMap) { if (this._currentRasterLayer) { // hide the previous layer - console.log("Removing previous layer", this._currentRasterLayer) map.removeLayer(this._currentRasterLayer) map.removeSource(this._currentRasterLayer) } @@ -185,4 +233,32 @@ export class MapLibreAdaptor implements MapState { this.removeCurrentLayer(map) this._currentRasterLayer = background?.id } + + private setMaxBounds(bbox: undefined | BBox) { + const map = this._maplibreMap.data + if (map === undefined) { + return + } + if (bbox) { + map.setMaxBounds(bbox.toLngLat()) + } else { + map.setMaxBounds(null) + } + } + + private setAllowMoving(allow: true | boolean | undefined) { + const map = this._maplibreMap.data + if (map === undefined) { + return + } + if (allow === false) { + for (const id of MapLibreAdaptor.maplibre_control_handlers) { + map[id].disable() + } + } else { + for (const id of MapLibreAdaptor.maplibre_control_handlers) { + map[id].enable() + } + } + } } diff --git a/UI/Map/MaplibreMap.svelte b/UI/Map/MaplibreMap.svelte index 25d9462d23..dcd1a1cd67 100644 --- a/UI/Map/MaplibreMap.svelte +++ b/UI/Map/MaplibreMap.svelte @@ -8,8 +8,6 @@ import { Map } from "@onsvisual/svelte-maps"; import type { Map as MaplibreMap } from "maplibre-gl"; import type { Writable } from "svelte/store"; - import type Loc from "../../Models/Loc"; - import { UIEventSource } from "../../Logic/UIEventSource"; /** @@ -30,7 +28,6 @@
diff --git a/UI/Map/ShowDataLayer.ts b/UI/Map/ShowDataLayer.ts index 904fa59d0f..c5e1ee15a5 100644 --- a/UI/Map/ShowDataLayer.ts +++ b/UI/Map/ShowDataLayer.ts @@ -1,108 +1,294 @@ import { ImmutableStore, Store } from "../../Logic/UIEventSource" import type { Map as MlMap } from "maplibre-gl" import { Marker } from "maplibre-gl" -import { ShowDataLayerOptions } from "../ShowDataLayer/ShowDataLayerOptions" +import { ShowDataLayerOptions } from "./ShowDataLayerOptions" import { GeoOperations } from "../../Logic/GeoOperations" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import PointRenderingConfig from "../../Models/ThemeConfig/PointRenderingConfig" -import { OsmFeature, OsmTags } from "../../Models/OsmFeature" +import { OsmTags } from "../../Models/OsmFeature" import FeatureSource from "../../Logic/FeatureSource/FeatureSource" import { BBox } from "../../Logic/BBox" - +import { Feature, LineString } from "geojson" +import ScrollableFullScreen from "../Base/ScrollableFullScreen" +import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" +import { Utils } from "../../Utils" +import * as range_layer from "../../assets/layers/range/range.json" +import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" class PointRenderingLayer { private readonly _config: PointRenderingConfig private readonly _fetchStore?: (id: string) => Store private readonly _map: MlMap + private readonly _onClick: (id: string) => void + private readonly _allMarkers: Map = new Map() constructor( map: MlMap, features: FeatureSource, config: PointRenderingConfig, - fetchStore?: (id: string) => Store + visibility?: Store, + fetchStore?: (id: string) => Store, + onClick?: (id: string) => void ) { this._config = config this._map = map this._fetchStore = fetchStore - const cache: Map = new Map() + this._onClick = onClick const self = this - features.features.addCallbackAndRunD((features) => { - const unseenKeys = new Set(cache.keys()) - for (const { feature } of features) { - const id = feature.properties.id + + features.features.addCallbackAndRunD((features) => self.updateFeatures(features)) + visibility?.addCallbackAndRunD((visible) => self.setVisibility(visible)) + } + + private updateFeatures(features: Feature[]) { + const cache = this._allMarkers + const unseenKeys = new Set(cache.keys()) + for (const location of this._config.location) { + for (const feature of features) { + const loc = GeoOperations.featureToCoordinateWithRenderingType( + feature, + location + ) + if (loc === undefined) { + continue + } + const id = feature.properties.id + "-" + location unseenKeys.delete(id) - const loc = GeoOperations.centerpointCoordinates(feature) + if (cache.has(id)) { - console.log("Not creating a marker for ", id) const cached = cache.get(id) const oldLoc = cached.getLngLat() - console.log("OldLoc vs newLoc", oldLoc, loc) if (loc[0] !== oldLoc.lng && loc[1] !== oldLoc.lat) { cached.setLngLat(loc) - console.log("MOVED") } continue } - console.log("Creating a marker for ", id) - const marker = self.addPoint(feature) + const marker = this.addPoint(feature, loc) cache.set(id, marker) } + } - for (const unseenKey of unseenKeys) { - cache.get(unseenKey).remove() - cache.delete(unseenKey) - } - }) + for (const unseenKey of unseenKeys) { + cache.get(unseenKey).remove() + cache.delete(unseenKey) + } } - private addPoint(feature: OsmFeature): Marker { + private setVisibility(visible: boolean) { + for (const marker of this._allMarkers.values()) { + if (visible) { + marker.getElement().classList.remove("hidden") + } else { + marker.getElement().classList.add("hidden") + } + } + } + + private addPoint(feature: Feature, loc: [number, number]): Marker { let store: Store if (this._fetchStore) { store = this._fetchStore(feature.properties.id) } else { - store = new ImmutableStore(feature.properties) + store = new ImmutableStore(feature.properties) } - const { html, iconAnchor } = this._config.GenerateLeafletStyle(store, true) + const { html, iconAnchor } = this._config.RenderIcon(store, true) html.SetClass("marker") const el = html.ConstructElement() - el.addEventListener("click", function () { - window.alert("Hello world!") - }) + if (this._onClick) { + const self = this + el.addEventListener("click", function () { + self._onClick(feature.properties.id) + }) + } - return new Marker(el) - .setLngLat(GeoOperations.centerpointCoordinates(feature)) - .setOffset(iconAnchor) - .addTo(this._map) + return new Marker(el).setLngLat(loc).setOffset(iconAnchor).addTo(this._map) } } -export class ShowDataLayer { +class LineRenderingLayer { + /** + * These are dynamic properties + * @private + */ + private static readonly lineConfigKeys = [ + "color", + "width", + "lineCap", + "offset", + "fill", + "fillColor", + ] + private readonly _map: MlMap + private readonly _config: LineRenderingConfig + private readonly _visibility?: Store + private readonly _fetchStore?: (id: string) => Store + private readonly _onClick?: (id: string) => void + private readonly _layername: string + + constructor( + map: MlMap, + features: FeatureSource, + layername: string, + config: LineRenderingConfig, + visibility?: Store, + fetchStore?: (id: string) => Store, + onClick?: (id: string) => void + ) { + this._layername = layername + this._map = map + this._config = config + this._visibility = visibility + this._fetchStore = fetchStore + this._onClick = onClick + const self = this + features.features.addCallbackAndRunD((features) => self.update(features)) + } + + private async update(features: Feature[]) { + const map = this._map + while (!map.isStyleLoaded()) { + await Utils.waitFor(100) + } + map.addSource(this._layername, { + type: "geojson", + data: { + type: "FeatureCollection", + features, + }, + promoteId: "id", + }) + for (let i = 0; i < features.length; i++) { + const feature = features[i] + const id = feature.properties.id ?? "" + i + const tags = this._fetchStore(id) + tags.addCallbackAndRunD((properties) => { + const config = this._config + + const calculatedProps = {} + for (const key of LineRenderingLayer.lineConfigKeys) { + const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt + calculatedProps[key] = v + } + + map.setFeatureState({ source: this._layername, id }, calculatedProps) + }) + } + + map.addLayer({ + source: this._layername, + id: this._layername + "_line", + type: "line", + filter: ["in", ["geometry-type"], ["literal", ["LineString", "MultiLineString"]]], + layout: {}, + paint: { + "line-color": ["feature-state", "color"], + "line-width": ["feature-state", "width"], + "line-offset": ["feature-state", "offset"], + }, + }) + + /*[ + "color", + "width", + "dashArray", + "lineCap", + "offset", + "fill", + "fillColor", + ]*/ + map.addLayer({ + source: this._layername, + id: this._layername + "_polygon", + type: "fill", + filter: ["in", ["geometry-type"], ["literal", ["Polygon", "MultiPolygon"]]], + layout: {}, + paint: { + "fill-color": ["feature-state", "fillColor"], + }, + }) + } +} + +export default class ShowDataLayer { private readonly _map: Store - private _options: ShowDataLayerOptions & { layer: LayerConfig } + private readonly _options: ShowDataLayerOptions & { layer: LayerConfig } + private readonly _popupCache: Map constructor(map: Store, options: ShowDataLayerOptions & { layer: LayerConfig }) { this._map = map this._options = options + this._popupCache = new Map() const self = this map.addCallbackAndRunD((map) => self.initDrawFeatures(map)) } - private initDrawFeatures(map: MlMap) { - for (const pointRenderingConfig of this._options.layer.mapRendering) { - new PointRenderingLayer( - map, - this._options.features, - pointRenderingConfig, - this._options.fetchStore - ) + private static rangeLayer = new LayerConfig( + range_layer, + "ShowDataLayer.ts:range.json" + ) + + public static showRange( + map: Store, + features: FeatureSource, + doShowLayer?: Store + ): ShowDataLayer { + return new ShowDataLayer(map, { + layer: ShowDataLayer.rangeLayer, + features, + doShowLayer, + }) + } + + private openOrReusePopup(id: string): void { + if (this._popupCache.has(id)) { + this._popupCache.get(id).Activate() + return } + const tags = this._options.fetchStore(id) + if (!tags) { + return + } + const popup = this._options.buildPopup(tags, this._options.layer) + this._popupCache.set(id, popup) + popup.Activate() + } + + private zoomToCurrentFeatures(map: MlMap) { if (this._options.zoomToFeatures) { const features = this._options.features.features.data - const bbox = BBox.bboxAroundAll(features.map((f) => BBox.get(f.feature))) + const bbox = BBox.bboxAroundAll(features.map(BBox.get)) map.fitBounds(bbox.toLngLat(), { padding: { top: 10, bottom: 10, left: 10, right: 10 }, }) } } + + private initDrawFeatures(map: MlMap) { + const { features, doShowLayer, fetchStore, buildPopup } = this._options + const onClick = buildPopup === undefined ? undefined : (id) => this.openOrReusePopup(id) + for (const lineRenderingConfig of this._options.layer.lineRendering) { + new LineRenderingLayer( + map, + features, + "test", + lineRenderingConfig, + doShowLayer, + fetchStore, + onClick + ) + } + + for (const pointRenderingConfig of this._options.layer.mapRendering) { + new PointRenderingLayer( + map, + features, + pointRenderingConfig, + doShowLayer, + fetchStore, + onClick + ) + } + features.features.addCallbackAndRunD((_) => this.zoomToCurrentFeatures(map)) + } } diff --git a/UI/ShowDataLayer/ShowDataLayerOptions.ts b/UI/Map/ShowDataLayerOptions.ts similarity index 95% rename from UI/ShowDataLayer/ShowDataLayerOptions.ts rename to UI/Map/ShowDataLayerOptions.ts index 9a66d0d494..dde88b6631 100644 --- a/UI/ShowDataLayer/ShowDataLayerOptions.ts +++ b/UI/Map/ShowDataLayerOptions.ts @@ -33,5 +33,5 @@ export interface ShowDataLayerOptions { /** * Function which fetches the relevant store */ - fetchStore?: (id: string) => Store + fetchStore?: (id: string) => UIEventSource } diff --git a/UI/ShowDataLayer/ShowDataMultiLayer.ts b/UI/Map/ShowDataMultiLayer.ts similarity index 73% rename from UI/ShowDataLayer/ShowDataMultiLayer.ts rename to UI/Map/ShowDataMultiLayer.ts index ef2eabab4f..84710b6fc6 100644 --- a/UI/ShowDataLayer/ShowDataMultiLayer.ts +++ b/UI/Map/ShowDataMultiLayer.ts @@ -6,18 +6,21 @@ import ShowDataLayer from "./ShowDataLayer" import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter" import FilteredLayer from "../../Models/FilteredLayer" import { ShowDataLayerOptions } from "./ShowDataLayerOptions" - +import { Map as MlMap } from "maplibre-gl" export default class ShowDataMultiLayer { - constructor(options: ShowDataLayerOptions & { layers: Store }) { + constructor( + map: Store, + options: ShowDataLayerOptions & { layers: Store } + ) { new PerLayerFeatureSourceSplitter( options.layers, (perLayer) => { const newOptions = { ...options, - layerToShow: perLayer.layer.layerDef, + layer: perLayer.layer.layerDef, features: perLayer, } - new ShowDataLayer(newOptions) + new ShowDataLayer(map, newOptions) }, options.features ) diff --git a/UI/NewPoint/ConfirmLocationOfPoint.ts b/UI/NewPoint/ConfirmLocationOfPoint.ts index 8aed2d01d0..47a203ecbc 100644 --- a/UI/NewPoint/ConfirmLocationOfPoint.ts +++ b/UI/NewPoint/ConfirmLocationOfPoint.ts @@ -13,13 +13,13 @@ import Toggle from "../Input/Toggle" import SimpleAddUI, { PresetInfo } from "../BigComponents/SimpleAddUI" import Img from "../Base/Img" import Title from "../Base/Title" -import { GlobalFilter } from "../../Logic/State/MapState" import { VariableUiElement } from "../Base/VariableUIElement" import { Tag } from "../../Logic/Tags/Tag" import { WayId } from "../../Models/OsmFeature" import { Translation } from "../i18n/Translation" -import { Feature } from "geojson"; -import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers"; +import { Feature } from "geojson" +import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers" +import { GlobalFilter } from "../../Logic/State/GlobalFilter" export default class ConfirmLocationOfPoint extends Combine { constructor( @@ -69,7 +69,7 @@ export default class ConfirmLocationOfPoint extends Combine { let snapToFeatures: UIEventSource = undefined let mapBounds: UIEventSource = undefined if (preset.preciseInput.snapToLayers && preset.preciseInput.snapToLayers.length > 0) { - snapToFeatures = new UIEventSource< Feature[]>([]) + snapToFeatures = new UIEventSource([]) mapBounds = new UIEventSource(undefined) } @@ -110,9 +110,7 @@ export default class ConfirmLocationOfPoint extends Combine { console.log("Snapping to", layerId) state.featurePipeline .GetFeaturesWithin(layerId, bbox) - ?.forEach((feats) => - allFeatures.push(...feats) - ) + ?.forEach((feats) => allFeatures.push(...(feats))) }) console.log("Snapping to", allFeatures) snapToFeatures.setData(allFeatures) diff --git a/UI/AllTagsPanel.svelte b/UI/Popup/AllTagsPanel.svelte similarity index 88% rename from UI/AllTagsPanel.svelte rename to UI/Popup/AllTagsPanel.svelte index 4eab95c50e..b2beb2cef0 100644 --- a/UI/AllTagsPanel.svelte +++ b/UI/Popup/AllTagsPanel.svelte @@ -1,7 +1,7 @@ + + +
+ +
+ +
+ +
+ +
+
+ +
+ + + mapproperties.zoom.update(z => z+1)}> + + + mapproperties.zoom.update(z => z-1)}> + + + + + + new GeolocationControl(geolocation, mapproperties).SetClass("block w-8 h-8")}> + + +
+ +
+
+ diff --git a/UI/i18n/Translation.ts b/UI/i18n/Translation.ts index bed4c6c6a7..c18583295c 100644 --- a/UI/i18n/Translation.ts +++ b/UI/i18n/Translation.ts @@ -2,7 +2,6 @@ import Locale from "./Locale" import { Utils } from "../../Utils" import BaseUIElement from "../BaseUIElement" import LinkToWeblate from "../Base/LinkToWeblate" -import { SvelteComponent } from "svelte" export class Translation extends BaseUIElement { public static forcedLanguage = undefined @@ -299,7 +298,7 @@ export class Translation extends BaseUIElement { } } -export class TypedTranslation extends Translation { +export class TypedTranslation> extends Translation { constructor(translations: Record, context?: string) { super(translations, context) } diff --git a/all_themes_index.ts b/all_themes_index.ts index fb15e26711..d9f8d534f7 100644 --- a/all_themes_index.ts +++ b/all_themes_index.ts @@ -1,5 +1,3 @@ -import MinimapImplementation from "./UI/Base/MinimapImplementation" - import { Utils } from "./Utils" import AllThemesGui from "./UI/AllThemesGui" import { QueryParameters } from "./Logic/Web/QueryParameters" @@ -46,7 +44,6 @@ if (mode.data === "statistics") { new FixedUiElement("").AttachTo("centermessage") new StatisticsGUI().SetClass("w-full h-full pointer-events-auto").AttachTo("topleft-tools") } else if (mode.data === "pdf") { - MinimapImplementation.initialize() new FixedUiElement("").AttachTo("centermessage") const div = document.createElement("div") div.id = "extra_div_for_maps" diff --git a/assets/layers/cluster_style/cluster_style.json b/assets/layers/cluster_style/cluster_style.json deleted file mode 100644 index 081cbb5d34..0000000000 --- a/assets/layers/cluster_style/cluster_style.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "id": "cluster_style", - "description": "The style for the clustering in all themes. Enable `debug=true` to peak into clustered tiles", - "source": { - "osmTags": "tileId~*" - }, - "title": "Clustered data", - "tagRenderings": [ - "all_tags" - ], - "mapRendering": [ - { - "label": { - "render": "
{showCount}
", - "mappings": [ - { - "if": "showCount>1000", - "then": "
{kilocount}K
" - } - ] - }, - "location": [ - "point" - ] - }, - { - "color": { - "render": "#3c3", - "mappings": [ - { - "if": "showCount>200", - "then": "#f33" - }, - { - "if": "showCount>100", - "then": "#c93" - }, - { - "if": "showCount>50", - "then": "#cc3" - } - ] - }, - "width": { - "render": "1" - } - } - ] -} \ No newline at end of file diff --git a/assets/layers/conflation/conflation.json b/assets/layers/conflation/conflation.json index ffafc9bca4..5cc23d0546 100644 --- a/assets/layers/conflation/conflation.json +++ b/assets/layers/conflation/conflation.json @@ -2,14 +2,7 @@ "id": "conflation", "description": "If the import-button moves OSM points, the imported way points or conflates, a preview is shown. This layer defines how this preview is rendered. This layer cannot be included in a theme.", "minzoom": 1, - "source": { - "osmTags": { - "or": [ - "move=yes", - "newpoint=yes" - ] - } - }, + "source": "special", "name": "Conflation", "title": "Conflation", "mapRendering": [ @@ -86,4 +79,4 @@ } } ] -} \ No newline at end of file +} diff --git a/assets/layers/current_view/current_view.json b/assets/layers/current_view/current_view.json index b3649f4bd2..da1e0f47fa 100644 --- a/assets/layers/current_view/current_view.json +++ b/assets/layers/current_view/current_view.json @@ -1,10 +1,7 @@ { "id": "current_view", "description": "A meta-layer which contains one single feature, namely the BBOX of the current map view. This can be used to trigger special actions. If a popup is defined for this layer, this popup will be accessible via an extra button on screen.\n\nThe icon on the button is the default icon of the layer, but can be customized by detecting 'button=yes'.", - "source": { - "osmTags": "current_view=yes", - "maxCacheAge": 0 - }, + "source": "special", "shownByDefault": false, "title": "Current View", "tagRenderings": [], @@ -13,4 +10,4 @@ "color": "#cccc0088" } ] -} \ No newline at end of file +} diff --git a/assets/layers/filters/filters.json b/assets/layers/filters/filters.json index 579ceec66d..d00f296012 100644 --- a/assets/layers/filters/filters.json +++ b/assets/layers/filters/filters.json @@ -2,9 +2,7 @@ "id": "filters", "description": "This layer acts as library for common filters", "mapRendering": null, - "source": { - "osmTags": "id~*" - }, + "source": "special:library", "filter": [ { "id": "open_now", diff --git a/assets/layers/gps_location/gps_location.json b/assets/layers/gps_location/gps_location.json index 9d11abeab5..a5beb0913d 100644 --- a/assets/layers/gps_location/gps_location.json +++ b/assets/layers/gps_location/gps_location.json @@ -2,10 +2,7 @@ "id": "gps_location", "description": "Meta layer showing the current location of the user. Add this to your theme and override the icon to change the appearance of the current location. The object will always have `id=gps` and will have _all_ the properties included in the [`Coordinates`-object](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates) returned by the browser.", "minzoom": 0, - "source": { - "osmTags": "id=gps", - "maxCacheAge": 0 - }, + "source": "special", "mapRendering": [ { "icon": { @@ -38,4 +35,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/assets/layers/gps_location_history/gps_location_history.json b/assets/layers/gps_location_history/gps_location_history.json index 3e4f0f5826..34167beb5f 100644 --- a/assets/layers/gps_location_history/gps_location_history.json +++ b/assets/layers/gps_location_history/gps_location_history.json @@ -3,11 +3,7 @@ "description": "Meta layer which contains the previous locations of the user as single points. This is mainly for technical reasons, e.g. to keep match the distance to the modified object", "minzoom": 1, "name": null, - "source": { - "osmTags": "user:location=yes", - "#": "Cache is disabled here as these points are kept seperately", - "maxCacheAge": 0 - }, + "source": "special", "shownByDefault": false, "mapRendering": [ { @@ -19,4 +15,4 @@ "iconSize": "5,5,center" } ] -} \ No newline at end of file +} diff --git a/assets/layers/gps_track/gps_track.json b/assets/layers/gps_track/gps_track.json index 8d4243403d..94bb2a40b8 100644 --- a/assets/layers/gps_track/gps_track.json +++ b/assets/layers/gps_track/gps_track.json @@ -2,10 +2,7 @@ "id": "gps_track", "description": "Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track.", "minzoom": 0, - "source": { - "osmTags": "id=location_track", - "maxCacheAge": 0 - }, + "source": "special", "title": { "render": "Your travelled path" }, diff --git a/assets/layers/home_location/home_location.json b/assets/layers/home_location/home_location.json index 74276dc09a..b9fc29509b 100644 --- a/assets/layers/home_location/home_location.json +++ b/assets/layers/home_location/home_location.json @@ -2,10 +2,7 @@ "id": "home_location", "description": "Meta layer showing the home location of the user. The home location can be set in the [profile settings](https://www.openstreetmap.org/profile/edit) of OpenStreetMap.", "minzoom": 0, - "source": { - "osmTags": "user:home=yes", - "maxCacheAge": 0 - }, + "source":"special", "mapRendering": [ { "icon": { @@ -20,4 +17,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index d128dfb9bd..0aa21f6a3f 100644 --- a/assets/layers/icons/icons.json +++ b/assets/layers/icons/icons.json @@ -3,9 +3,7 @@ "description": { "en": "A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI" }, - "source": { - "osmTags": "id~*" - }, + "source":"special:library", "title": null, "tagRenderings": [ { @@ -127,4 +125,4 @@ } ], "mapRendering": null -} \ No newline at end of file +} diff --git a/assets/layers/id_presets/id_presets.json b/assets/layers/id_presets/id_presets.json index caf39d97a0..152dbc7be6 100644 --- a/assets/layers/id_presets/id_presets.json +++ b/assets/layers/id_presets/id_presets.json @@ -4,9 +4,7 @@ "en": "Layer containing various presets and questions generated by ID. These are meant to be reused in other layers by importing the tagRenderings with `id_preset." }, "#dont-translate": "*", - "source": { - "osmTags": "id~*" - }, + "source": "special:library", "title": null, "mapRendering": null, "tagRenderings": [ @@ -20217,4 +20215,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/assets/layers/import_candidate/import_candidate.json b/assets/layers/import_candidate/import_candidate.json index 70720181e7..52ed5ba3b9 100644 --- a/assets/layers/import_candidate/import_candidate.json +++ b/assets/layers/import_candidate/import_candidate.json @@ -1,11 +1,7 @@ { "id": "import_candidate", "description": "Layer used in the importHelper", - "source": { - "osmTags": { - "and": [] - } - }, + "source":"special", "mapRendering": [ { "location": [ @@ -23,4 +19,4 @@ "render": "{all_tags()}" } ] -} \ No newline at end of file +} diff --git a/assets/layers/left_right_style/left_right_style.json b/assets/layers/left_right_style/left_right_style.json deleted file mode 100644 index 9b8f82d136..0000000000 --- a/assets/layers/left_right_style/left_right_style.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "id": "left_right_style", - "description": "Special meta-style which will show one single line, either on the left or on the right depending on the id. This is used in the small popups with left_right roads. Cannot be included in a theme", - "source": { - "osmTags": { - "or": [ - "id=left", - "id=right" - ] - } - }, - "mapRendering": [ - { - "width": 15, - "color": { - "render": "#ff000088", - "mappings": [ - { - "if": "id=left", - "then": "#0000ff88" - } - ] - }, - "offset": { - "render": "-15", - "mappings": [ - { - "if": "id=right", - "then": "15" - } - ] - } - } - ] -} \ No newline at end of file diff --git a/assets/layers/matchpoint/matchpoint.json b/assets/layers/matchpoint/matchpoint.json index 0a3d3080fe..5814561b8c 100644 --- a/assets/layers/matchpoint/matchpoint.json +++ b/assets/layers/matchpoint/matchpoint.json @@ -1,11 +1,7 @@ { "id": "matchpoint", "description": "The default rendering for a locationInput which snaps onto another object", - "source": { - "osmTags": { - "and": [] - } - }, + "source":"special", "mapRendering": [ { "location": [ @@ -15,4 +11,4 @@ "icon": "./assets/svg/crosshair-empty.svg" } ] -} \ No newline at end of file +} diff --git a/assets/layers/range/range.json b/assets/layers/range/range.json new file mode 100644 index 0000000000..1ea7aa4590 --- /dev/null +++ b/assets/layers/range/range.json @@ -0,0 +1,14 @@ +{ + "id": "range", + "description": "Meta-layer, simply showing a bbox in red", + "title": null, + "source": "special", + "name": null, + "mapRendering": [ + { + "width": 4, + "fill": "no", + "color": "#ff000088" + } + ] +} diff --git a/assets/layers/type_node/type_node.json b/assets/layers/type_node/type_node.json index 7f9228a510..20679e2d73 100644 --- a/assets/layers/type_node/type_node.json +++ b/assets/layers/type_node/type_node.json @@ -2,12 +2,9 @@ "id": "type_node", "description": "This is a priviliged meta_layer which exports _every_ point in OSM. This only works if zoomed below the point that the full tile is loaded (and not loaded via Overpass). Note that this point will also contain a property `parent_ways` which contains all the ways this node is part of as a list. This is mainly used for extremely specialized themes, which do advanced conflations. Expert use only.", "minzoom": 18, - "source": { - "osmTags": "id~node/.*", - "maxCacheAge": 0 - }, + "source": "special", "mapRendering": null, "name": "All OSM Nodes", "title": "OSM node {id}", "tagRendering": [] -} \ No newline at end of file +} diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json index bf45f4f69f..c990606485 100644 --- a/assets/layers/usersettings/usersettings.json +++ b/assets/layers/usersettings/usersettings.json @@ -6,9 +6,7 @@ "nl": "Een speciale lag die niet getoond wordt op de kaart, maar die de instellingen van de gebruiker weergeeft" }, "title": null, - "source": { - "osmTags": "id~*" - }, + "source": "special", "calculatedTags": [ "_mastodon_candidate_md=feat.properties._description.match(/\\[[^\\]]*\\]\\((.*(mastodon|en.osm.town).*)\\).*/)?.at(1)", "_d=feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? ''", @@ -320,4 +318,4 @@ } ], "mapRendering": null -} \ No newline at end of file +} diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 9ab2f31155..a518e8e702 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -706,24 +706,24 @@ video { bottom: 0px; } -.right-1\/3 { - right: 33.333333%; -} - -.top-4 { - top: 1rem; -} - .top-0 { top: 0px; } +.left-0 { + left: 0px; +} + .right-0 { right: 0px; } -.left-0 { - left: 0px; +.right-1\/3 { + right: 33.333333%; +} + +.top-4 { + top: 1rem; } .bottom-2 { @@ -766,10 +766,6 @@ video { margin: 1.25rem; } -.m-4 { - margin: 1rem; -} - .m-2 { margin: 0.5rem; } @@ -786,6 +782,10 @@ video { margin: 0.75rem; } +.m-4 { + margin: 1rem; +} + .m-1 { margin: 0.25rem; } @@ -827,18 +827,6 @@ video { margin-bottom: 1rem; } -.mt-2 { - margin-top: 0.5rem; -} - -.ml-0 { - margin-left: 0px; -} - -.mr-8 { - margin-right: 2rem; -} - .mt-1 { margin-top: 0.25rem; } @@ -871,6 +859,10 @@ video { margin-left: 0.25rem; } +.mt-2 { + margin-top: 0.5rem; +} + .mb-2 { margin-bottom: 0.5rem; } @@ -995,10 +987,6 @@ video { height: 100%; } -.h-64 { - height: 16rem; -} - .h-min { height: -webkit-min-content; height: min-content; @@ -1024,6 +1012,14 @@ video { height: 3rem; } +.h-screen { + height: 100vh; +} + +.h-7 { + height: 1.75rem; +} + .h-4 { height: 1rem; } @@ -1036,10 +1032,6 @@ video { height: 0.75rem; } -.h-screen { - height: 100vh; -} - .h-11 { height: 2.75rem; } @@ -1052,6 +1044,10 @@ video { height: 24rem; } +.h-64 { + height: 16rem; +} + .h-0 { height: 0px; } @@ -1084,14 +1080,6 @@ video { width: 100%; } -.w-24 { - width: 6rem; -} - -.w-1\/2 { - width: 50%; -} - .w-6 { width: 1.5rem; } @@ -1116,6 +1104,14 @@ video { width: 3rem; } +.w-screen { + width: 100vw; +} + +.w-7 { + width: 1.75rem; +} + .w-4 { width: 1rem; } @@ -1128,10 +1124,6 @@ video { width: 0.75rem; } -.w-screen { - width: 100vw; -} - .w-11 { width: 2.75rem; } @@ -1142,6 +1134,10 @@ video { width: fit-content; } +.w-1\/2 { + width: 50%; +} + .w-max { width: -webkit-max-content; width: max-content; @@ -1156,6 +1152,10 @@ video { width: min-content; } +.w-24 { + width: 6rem; +} + .w-auto { width: auto; } @@ -1189,10 +1189,6 @@ video { flex-grow: 1; } -.grow-0 { - flex-grow: 0; -} - .grow { flex-grow: 1; } @@ -1337,10 +1333,6 @@ video { overflow: scroll; } -.overflow-x-auto { - overflow-x: auto; -} - .overflow-y-auto { overflow-y: auto; } @@ -1376,6 +1368,14 @@ video { border-radius: 1.5rem; } +.rounded { + border-radius: 0.25rem; +} + +.rounded-full { + border-radius: 9999px; +} + .rounded-xl { border-radius: 0.75rem; } @@ -1384,22 +1384,14 @@ video { border-radius: 0.5rem; } -.rounded { - border-radius: 0.25rem; +.rounded-md { + border-radius: 0.375rem; } .rounded-2xl { border-radius: 1rem; } -.rounded-full { - border-radius: 9999px; -} - -.rounded-md { - border-radius: 0.375rem; -} - .rounded-sm { border-radius: 0.125rem; } @@ -1409,20 +1401,16 @@ video { border-bottom-left-radius: 0.25rem; } -.border { - border-width: 1px; -} - .border-2 { border-width: 2px; } -.border-4 { - border-width: 4px; +.border { + border-width: 1px; } -.border-b-4 { - border-bottom-width: 4px; +.border-4 { + border-width: 4px; } .border-l-4 { @@ -1455,6 +1443,11 @@ video { border-color: rgb(219 234 254 / var(--tw-border-opacity)); } +.border-red-500 { + --tw-border-opacity: 1; + border-color: rgb(239 68 68 / var(--tw-border-opacity)); +} + .border-gray-300 { --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); @@ -1499,11 +1492,6 @@ video { background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } -.bg-unsubtle { - --tw-bg-opacity: 1; - background-color: rgb(191 219 254 / var(--tw-bg-opacity)); -} - .bg-red-400 { --tw-bg-opacity: 1; background-color: rgb(248 113 113 / var(--tw-bg-opacity)); @@ -1519,11 +1507,6 @@ video { background-color: rgb(156 163 175 / var(--tw-bg-opacity)); } -.bg-indigo-100 { - --tw-bg-opacity: 1; - background-color: rgb(224 231 255 / var(--tw-bg-opacity)); -} - .bg-black { --tw-bg-opacity: 1; background-color: rgb(0 0 0 / var(--tw-bg-opacity)); @@ -1534,6 +1517,11 @@ video { background-color: rgb(229 231 235 / var(--tw-bg-opacity)); } +.bg-indigo-100 { + --tw-bg-opacity: 1; + background-color: rgb(224 231 255 / var(--tw-bg-opacity)); +} + .bg-gray-100 { --tw-bg-opacity: 1; background-color: rgb(243 244 246 / var(--tw-bg-opacity)); @@ -1558,14 +1546,14 @@ video { padding: 1rem; } -.p-2 { - padding: 0.5rem; -} - .p-1 { padding: 0.25rem; } +.p-2 { + padding: 0.5rem; +} + .p-3 { padding: 0.75rem; } @@ -1602,14 +1590,6 @@ video { padding-left: 0.75rem; } -.pl-1 { - padding-left: 0.25rem; -} - -.pr-1 { - padding-right: 0.25rem; -} - .pb-12 { padding-bottom: 3rem; } @@ -1634,6 +1614,14 @@ video { padding-bottom: 0.25rem; } +.pl-1 { + padding-left: 0.25rem; +} + +.pr-1 { + padding-right: 0.25rem; +} + .pt-2 { padding-top: 0.5rem; } @@ -1686,6 +1674,10 @@ video { text-align: center; } +.text-justify { + text-align: justify; +} + .align-baseline { vertical-align: baseline; } @@ -1808,6 +1800,11 @@ video { text-decoration-line: line-through; } +.antialiased { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + .opacity-50 { opacity: 0.5; } @@ -1907,15 +1904,14 @@ video { color: var(--subtle-detail-color-contrast); } -.bg-unsubtle { - background-color: var(--unsubtle-detail-color); - color: var(--unsubtle-detail-color-contrast); -} - .\[key\:string\] { key: string; } +.\[_\:string\] { + _: string; +} + :root { /* The main colour scheme of mapcomplete is configured here. * For a custom styling, set 'customCss' in your layoutConfig and overwrite some of these. @@ -2915,10 +2911,6 @@ input { width: 75%; } - .lg\:w-1\/4 { - width: 25%; - } - .lg\:w-1\/6 { width: 16.666667%; } diff --git a/index.ts b/index.ts index 43642982c5..3a8fec515e 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,5 @@ import { FixedUiElement } from "./UI/Base/FixedUiElement" import Combine from "./UI/Base/Combine" -import MinimapImplementation from "./UI/Base/MinimapImplementation" import { Utils } from "./Utils" import AllThemesGui from "./UI/AllThemesGui" import DetermineLayout from "./Logic/DetermineLayout" @@ -9,11 +8,7 @@ import DefaultGUI from "./UI/DefaultGUI" import State from "./State" import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation" import { DefaultGuiState } from "./UI/DefaultGuiState" -import { QueryParameters } from "./Logic/Web/QueryParameters" -import DashboardGui from "./UI/DashboardGui" -// Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts running from console -MinimapImplementation.initialize() ShowOverlayLayerImplementation.Implement() // Miscelleanous Utils.DisableLongPresses() @@ -38,16 +33,7 @@ class Init { // @ts-ignore window.mapcomplete_state = State.state - const mode = QueryParameters.GetQueryParameter( - "mode", - "map", - "The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'" - ) - if (mode.data === "dashboard") { - new DashboardGui(State.state, guiState).setup() - } else { - new DefaultGUI(State.state, guiState).setup() - } + new DefaultGUI(State.state, guiState).setup() } } diff --git a/package.json b/package.json index a224b3ac7d..b1b3564c41 100644 --- a/package.json +++ b/package.json @@ -83,10 +83,6 @@ "jest-mock": "^29.4.1", "jspdf": "^2.5.1", "latlon2country": "^1.2.6", - "leaflet": "^1.9.2", - "leaflet-polylineoffset": "^1.1.1", - "leaflet-providers": "^1.13.0", - "leaflet-simple-map-screenshoter": "^0.4.5", "libphonenumber-js": "^1.10.8", "lz-string": "^1.4.4", "mangrove-reviews-typescript": "^1.1.0", @@ -116,8 +112,6 @@ "@tsconfig/svelte": "^3.0.0", "@types/chai": "^4.3.0", "@types/geojson": "^7946.0.10", - "@types/leaflet-markercluster": "^1.0.3", - "@types/leaflet-providers": "^1.2.0", "@types/lz-string": "^1.3.34", "@types/node": "^18.11.18", "@types/papaparse": "^5.3.1", diff --git a/public/vendor/images/layers-2x.png b/public/vendor/images/layers-2x.png deleted file mode 100644 index 200c333dca9652ac4cba004d609e5af4eee168c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1259 zcmVFhCYNy;#0irRPomHqW|G1C*;4?@4#E?jH>?v@U%cy?3dQAc-DchXVErpOh~ z-jbon+tNbnl6hoEb;)TVk+%hTDDi_G%i3*RZ&15!$Fjr^f;Ke&A@|?=`2&+{zr+3a z{D*=t(`AXyS%X7N z%a#RZw6vD^t_rnM`L4E>m=U&R!A-&}nZIi$BOPvkhrCuUe@BN~-lRD)f44;J%TwgE zcze8u!PQ_NR7?o(NylLXVTfDO zxs5=@|GsYEsNo4M#nT%N!UE(?dnS)t2+{ELYAFp*3=iF=|EQnTp`#vlSXuGVraYo? z+RCzXo6h3qA8{KG?S4nE(lM+;Eb4nT3XV;7gcAxUi5m)`k5tv}cPy()8ZR3TLW3I- zAS^}cq-IJvL7a4RgR!yk@~RT%$lA7{L5ES*hyx)M4(yxI$Ub(4f)K|^v1>zvwQY!_ zIrWw8q9GS^!Dp~}+?mbnB6jDF8mVlbQ!jFKDY;w=7;XO{9bq7>LXGK24WA`;rL)_Z z)&j}pbV(;6gY;VMhbxgvn`X;6x}VUEE-7 z%)7j-%t8S=ZL3yc)HbXDAqJZvBTPoiW_A-+a8m3_Z?v{DN7Tnr#O_VUMT0UBt$;p` zDh6JbGHN8JJ*JN%y2%msb97@_S>9!%Egwk;?PEkU9ntz&3uR}%Fj5d$JHQbQb3}a{ zSzFT^#n=VInPpcAS}CNxj?_ zVscANk5Cfz(51EI1pz};AWWb|kgbYNb4wCEGUn3+eMUMV?1-{=I4TlmLJMot@rd07 zZuo2hk1ccu{YmGkcYdWAVdk{Z4Nm?^cTD&}jGm+Q1SYIXMwmG*oO*83&#>l%nbR`G zhh=lZ%xIb7kU3#;TBbfECrnC9P=-XpL|TG2BoZdj61*XiFbW8?1Z_wp%#;>${SUIy V$8qr;L*)Pf002ovPDHLkV1hYLS~36t diff --git a/public/vendor/images/layers.png b/public/vendor/images/layers.png deleted file mode 100644 index 1a72e5784b2b456eac5d7670738db80697af3377..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 696 zcmV;p0!RIcP)*@&l2<6p=!C&s@#ZL+%BQvF&b?w6S%wp=I>1QHj7AP5C)IWy#b znXXB;g;j=$a-tW89K%FbDceHVq&unY*Wx3L#=EGWH=rjqnp|4c_Ulec!ql3#G-5ZF zVlbBA@XP=)C8U&+Lrc)S4O5%1$&{(;7R^K(CSnvSr$v;+B$8q&7Bf|h$#PARo1^%M zf1H^nG-EiXVXr07OH(*8R)xa|FD;lXUlg_-%)~ZGsL2cX0NXaAzN2q%jqLRR6ruVk8`Jb7n#{`T;o@`F= z#3YcynIR^s83UNF3D!f5m#Mg)NJ24&Qfrqb&_z=yF;=B)#9Iq7u-@^O!(mW{D;qvr zPc)gVb%aowtS8m@ElL4A9G>w#ffQ~q{i&_i)*6f^)Sz|C?C>zb4Uo?H<-&Hz@a?J; z$ml@zGygWofb9$ZBj6aLjpLhsT2AzjOu=-*u_gSCUP001cn1^@s6z>|W`000GnNklGNuHDcIX17Zdjl&3`L?0sTjIws<{((Dh&g-s0<@jYQyl?D*X^?%13;ml^gy> ziMrY_^1WI=(g@LMizu=zCoA>C`6|QEq1eV92k*7m>G65*&@&6)aC&e}G zI)pf-Za|N`DT&Cn1J|o`19mumxW~hiKiKyc-P`S@q)rdTo84@QI@;0yXrG%9uhI>A zG5QHb6s4=<6xy{1 z@NMxEkryp{LS44%z$3lP^cX!9+2-;CTt3wM4(k*#C{aiIiLuB>jJj;KPhPzIC00bL zU3a#;aJld94lCW=`4&aAy8M7PY=HQ>O%$YEP4c4UY#CRxfgbE~(|uiI=YS8q;O9y6 zmIkXzR`}p7ti|PrM3a}WMnR=3NVnWdAAR>b9X@)DKL6=YsvmH%?I24wdq?Gh54_;# z$?_LvgjEdspdQlft#4CQ z`2Zyvy?*)N1Ftw|{_hakhG9WjS?Az@I@+IZ8JbWewR!XUK4&6346+d#~gsE0SY(LX8&JfY>Aj)RxGy96nwhs2rv zzW6pTnMpFkDSkT*a*6Dx|u@ds6ISVn0@^RmIsKZ5Y;bazbc;tTSq(kg(=481ODrPyNB6n z-$+U}(w$m6U6H$w17Bw+wDaFIe~GvNMYvnw31MpY0eQKT9l>SU``8k7w4)z!GZKMI z#_cEKq7k~i%nlK@6c-K?+R;B#5$?T#YpKD`t_4bAs^#E+@5QW$@OX3*`;(#{U^d-vY)&xEE>n5lYl&T?Am svg, -.leaflet-pane > canvas, -.leaflet-zoom-box, -.leaflet-image-layer, -.leaflet-layer { - position: absolute; - left: 0; - top: 0; -} - -.leaflet-container { - overflow: hidden; -} - -.leaflet-tile, -.leaflet-marker-icon, -.leaflet-marker-shadow { - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - -webkit-user-drag: none; -} - -/* Prevents IE11 from highlighting tiles in blue */ -.leaflet-tile::selection { - background: transparent; -} - -/* Safari renders non-retina tile on retina better with this, but Chrome is worse */ -.leaflet-safari .leaflet-tile { - image-rendering: -webkit-optimize-contrast; -} - -/* hack that prevents hw layers "stretching" when loading new tiles */ -.leaflet-safari .leaflet-tile-container { - width: 1600px; - height: 1600px; - -webkit-transform-origin: 0 0; -} - -.leaflet-marker-icon, -.leaflet-marker-shadow { - display: block; -} - -/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ -/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ -.leaflet-container .leaflet-overlay-pane svg, -.leaflet-container .leaflet-marker-pane img, -.leaflet-container .leaflet-shadow-pane img, -.leaflet-container .leaflet-tile-pane img, -.leaflet-container img.leaflet-image-layer, -.leaflet-container .leaflet-tile { - max-width: none !important; - max-height: none !important; -} - -.leaflet-container.leaflet-touch-zoom { - -ms-touch-action: pan-x pan-y; - touch-action: pan-x pan-y; -} - -.leaflet-container.leaflet-touch-drag { - -ms-touch-action: pinch-zoom; - /* Fallback for FF which doesn't support pinch-zoom */ - touch-action: none; - touch-action: pinch-zoom; -} - -.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { - -ms-touch-action: none; - touch-action: none; -} - -.leaflet-container { - -webkit-tap-highlight-color: transparent; -} - -.leaflet-container a { - -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); -} - -.leaflet-tile { - filter: inherit; - visibility: hidden; -} - -.leaflet-tile-loaded { - visibility: inherit; -} - -.leaflet-zoom-box { - width: 0; - height: 0; - -moz-box-sizing: border-box; - box-sizing: border-box; - z-index: 800; -} - -/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ -.leaflet-overlay-pane svg { - -moz-user-select: none; -} - -.leaflet-pane { - z-index: 400; -} - -.leaflet-tile-pane { - z-index: 200; -} - -.leaflet-overlay-pane { - z-index: 400; -} - -.leaflet-shadow-pane { - z-index: 500; -} - -.leaflet-marker-pane { - z-index: 600; -} - -.leaflet-tooltip-pane { - z-index: 650; -} - -.leaflet-popup-pane { - z-index: 700; -} - -.leaflet-map-pane canvas { - z-index: 100; -} - -.leaflet-map-pane svg { - z-index: 200; -} - -.leaflet-vml-shape { - width: 1px; - height: 1px; -} - -.lvml { - behavior: url(#default#VML); - display: inline-block; - position: absolute; -} - - -/* control positioning */ - -.leaflet-control { - position: relative; - z-index: 800; - pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ - pointer-events: auto; -} - -.leaflet-top, -.leaflet-bottom { - position: absolute; - z-index: 1000; - pointer-events: none; -} - -.leaflet-top { - top: 0; -} - -.leaflet-right { - right: 0; -} - -.leaflet-bottom { - bottom: 0; -} - -.leaflet-left { - left: 0; -} - -.leaflet-control { - float: left; - clear: both; -} - -.leaflet-right .leaflet-control { - float: right; -} - -.leaflet-top .leaflet-control { - margin-top: 10px; -} - -.leaflet-bottom .leaflet-control { - margin-bottom: 10px; -} - -.leaflet-left .leaflet-control { - margin-left: 10px; -} - -.leaflet-right .leaflet-control { - margin-right: 10px; -} - - -/* zoom and fade animations */ - -.leaflet-fade-anim .leaflet-tile { - will-change: opacity; -} - -.leaflet-fade-anim .leaflet-popup { - opacity: 0; - -webkit-transition: opacity 0.2s linear; - -moz-transition: opacity 0.2s linear; - transition: opacity 0.2s linear; -} - -.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { - opacity: 1; -} - -.leaflet-zoom-animated { - -webkit-transform-origin: 0 0; - -ms-transform-origin: 0 0; - transform-origin: 0 0; -} - -.leaflet-zoom-anim .leaflet-zoom-animated { - will-change: transform; -} - -.leaflet-zoom-anim .leaflet-zoom-animated { - -webkit-transition: -webkit-transform 0.25s cubic-bezier(0, 0, 0.25, 1); - -moz-transition: -moz-transform 0.25s cubic-bezier(0, 0, 0.25, 1); - transition: transform 0.25s cubic-bezier(0, 0, 0.25, 1); -} - -.leaflet-zoom-anim .leaflet-tile, -.leaflet-pan-anim .leaflet-tile { - -webkit-transition: none; - -moz-transition: none; - transition: none; -} - -.leaflet-zoom-anim .leaflet-zoom-hide { - visibility: hidden; -} - - -/* cursors */ - -.leaflet-interactive { - cursor: pointer; -} - -.leaflet-grab { - cursor: -webkit-grab; - cursor: -moz-grab; - cursor: grab; -} - -.leaflet-crosshair, -.leaflet-crosshair .leaflet-interactive { - cursor: crosshair; -} - -.leaflet-popup-pane, -.leaflet-control { - cursor: auto; -} - -.leaflet-dragging .leaflet-grab, -.leaflet-dragging .leaflet-grab .leaflet-interactive, -.leaflet-dragging .leaflet-marker-draggable { - cursor: move; - cursor: -webkit-grabbing; - cursor: -moz-grabbing; - cursor: grabbing; -} - -/* marker & overlays interactivity */ -.leaflet-marker-icon, -.leaflet-marker-shadow, -.leaflet-image-layer, -.leaflet-pane > svg path, -.leaflet-tile-container { - pointer-events: none; -} - -.leaflet-marker-icon.leaflet-interactive, -.leaflet-image-layer.leaflet-interactive, -.leaflet-pane > svg path.leaflet-interactive, -svg.leaflet-image-layer.leaflet-interactive path { - pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ - pointer-events: auto; -} - -/* visual tweaks */ - -.leaflet-container { - background: #ddd; - outline: 0; -} - -.leaflet-container a { - color: #0078A8; -} - -.leaflet-container a.leaflet-active { - outline: 2px solid orange; -} - -.leaflet-zoom-box { - border: 2px dotted #38f; - background: rgba(255, 255, 255, 0.5); -} - - -/* general typography */ -.leaflet-container { - font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; -} - - -/* general toolbar styles */ - -.leaflet-bar { - box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65); - border-radius: 4px; -} - -.leaflet-bar a, -.leaflet-bar a:hover { - background-color: #fff; - border-bottom: 1px solid #ccc; - width: 26px; - height: 26px; - line-height: 26px; - display: block; - text-align: center; - text-decoration: none; - color: black; -} - -.leaflet-bar a, -.leaflet-control-layers-toggle { - background-position: 50% 50%; - background-repeat: no-repeat; - display: block; -} - -.leaflet-bar a:hover { - background-color: #f4f4f4; -} - -.leaflet-bar a:first-child { - border-top-left-radius: 4px; - border-top-right-radius: 4px; -} - -.leaflet-bar a:last-child { - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - border-bottom: none; -} - -.leaflet-bar a.leaflet-disabled { - cursor: default; - background-color: #f4f4f4; - color: #bbb; -} - -.leaflet-touch .leaflet-bar a { - width: 30px; - height: 30px; - line-height: 30px; -} - -.leaflet-touch .leaflet-bar a:first-child { - border-top-left-radius: 2px; - border-top-right-radius: 2px; -} - -.leaflet-touch .leaflet-bar a:last-child { - border-bottom-left-radius: 2px; - border-bottom-right-radius: 2px; -} - -/* zoom control */ - -.leaflet-control-zoom-in, -.leaflet-control-zoom-out { - font: bold 18px 'Lucida Console', Monaco, monospace; - text-indent: 1px; -} - -.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { - font-size: 22px; -} - - -/* layers control */ - -.leaflet-control-layers { - box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4); - background: #fff; - border-radius: 5px; -} - -.leaflet-control-layers-toggle { - background-image: url(images/layers.png); - width: 36px; - height: 36px; -} - -.leaflet-retina .leaflet-control-layers-toggle { - background-image: url(images/layers-2x.png); - background-size: 26px 26px; -} - -.leaflet-touch .leaflet-control-layers-toggle { - width: 44px; - height: 44px; -} - -.leaflet-control-layers .leaflet-control-layers-list, -.leaflet-control-layers-expanded .leaflet-control-layers-toggle { - display: none; -} - -.leaflet-control-layers-expanded .leaflet-control-layers-list { - display: block; - position: relative; -} - -.leaflet-control-layers-expanded { - padding: 6px 10px 6px 6px; - color: #333; - background: #fff; -} - -.leaflet-control-layers-scrollbar { - overflow-y: scroll; - overflow-x: hidden; - padding-right: 5px; -} - -.leaflet-control-layers-selector { - margin-top: 2px; - position: relative; - top: 1px; -} - -.leaflet-control-layers label { - display: block; -} - -.leaflet-control-layers-separator { - height: 0; - border-top: 1px solid #ddd; - margin: 5px -10px 5px -6px; -} - -/* Default icon URLs */ -.leaflet-default-icon-path { - background-image: url(images/marker-icon.png); -} - - -/* attribution and scale controls */ - -.leaflet-container .leaflet-control-attribution { - background: #fff; - background: rgba(255, 255, 255, 0.7); - margin: 0; -} - -.leaflet-control-attribution, -.leaflet-control-scale-line { - padding: 0 5px; - color: #333; -} - -.leaflet-control-attribution a { - text-decoration: none; -} - -.leaflet-control-attribution a:hover { - text-decoration: underline; -} - -.leaflet-container .leaflet-control-attribution, -.leaflet-container .leaflet-control-scale { - font-size: 11px; -} - -.leaflet-left .leaflet-control-scale { - margin-left: 5px; -} - -.leaflet-bottom .leaflet-control-scale { - margin-bottom: 5px; -} - -.leaflet-control-scale-line { - border: 2px solid #777; - border-top: none; - line-height: 1.1; - padding: 2px 5px 1px; - font-size: 11px; - white-space: nowrap; - overflow: hidden; - -moz-box-sizing: border-box; - box-sizing: border-box; - - background: #fff; - background: rgba(255, 255, 255, 0.5); -} - -.leaflet-control-scale-line:not(:first-child) { - border-top: 2px solid #777; - border-bottom: none; - margin-top: -2px; -} - -.leaflet-control-scale-line:not(:first-child):not(:last-child) { - border-bottom: 2px solid #777; -} - -.leaflet-touch .leaflet-control-attribution, -.leaflet-touch .leaflet-control-layers, -.leaflet-touch .leaflet-bar { - box-shadow: none; -} - -.leaflet-touch .leaflet-control-layers, -.leaflet-touch .leaflet-bar { - border: 2px solid rgba(0, 0, 0, 0.2); - background-clip: padding-box; -} - - -/* popup */ - -.leaflet-popup { - position: absolute; - text-align: center; - margin-bottom: 20px; -} - -.leaflet-popup-content-wrapper { - padding: 1px; - text-align: left; - border-radius: 12px; -} - -.leaflet-popup-content { - margin: 13px 19px; - line-height: 1.4; -} - -.leaflet-popup-content p { - margin: 18px 0; -} - -.leaflet-popup-tip-container { - width: 40px; - height: 20px; - position: absolute; - left: 50%; - margin-left: -20px; - overflow: hidden; - pointer-events: none; -} - -.leaflet-popup-tip { - width: 17px; - height: 17px; - padding: 1px; - - margin: -10px auto 0; - - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg); -} - -.leaflet-popup-content-wrapper, -.leaflet-popup-tip { - background: white; - color: #333; - box-shadow: 0 3px 14px rgba(0, 0, 0, 0.4); -} - -.leaflet-container a.leaflet-popup-close-button { - position: absolute; - top: 0; - right: 0; - padding: 4px 4px 0 0; - border: none; - text-align: center; - width: 18px; - height: 14px; - font: 16px/14px Tahoma, Verdana, sans-serif; - color: #c3c3c3; - text-decoration: none; - font-weight: bold; - background: transparent; -} - -.leaflet-container a.leaflet-popup-close-button:hover { - color: #999; -} - -.leaflet-popup-scrolled { - overflow: auto; - border-bottom: 1px solid #ddd; - border-top: 1px solid #ddd; -} - -.leaflet-oldie .leaflet-popup-content-wrapper { - zoom: 1; -} - -.leaflet-oldie .leaflet-popup-tip { - width: 24px; - margin: 0 auto; - - -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; - filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); -} - -.leaflet-oldie .leaflet-popup-tip-container { - margin-top: -1px; -} - -.leaflet-oldie .leaflet-control-zoom, -.leaflet-oldie .leaflet-control-layers, -.leaflet-oldie .leaflet-popup-content-wrapper, -.leaflet-oldie .leaflet-popup-tip { - border: 1px solid #999; -} - - -/* div icon */ - -.leaflet-div-icon { - background: #fff; - border: 1px solid #666; -} - - -/* Tooltip */ -/* Base styles for the element that has a tooltip */ -.leaflet-tooltip { - position: absolute; - padding: 6px; - background-color: #fff; - border: 1px solid #fff; - border-radius: 3px; - color: #222; - white-space: nowrap; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - pointer-events: none; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); -} - -.leaflet-tooltip.leaflet-clickable { - cursor: pointer; - pointer-events: auto; -} - -.leaflet-tooltip-top:before, -.leaflet-tooltip-bottom:before, -.leaflet-tooltip-left:before, -.leaflet-tooltip-right:before { - position: absolute; - pointer-events: none; - border: 6px solid transparent; - background: transparent; - content: ""; -} - -/* Directions */ - -.leaflet-tooltip-bottom { - margin-top: 6px; -} - -.leaflet-tooltip-top { - margin-top: -6px; -} - -.leaflet-tooltip-bottom:before, -.leaflet-tooltip-top:before { - left: 50%; - margin-left: -6px; -} - -.leaflet-tooltip-top:before { - bottom: 0; - margin-bottom: -12px; - border-top-color: #fff; -} - -.leaflet-tooltip-bottom:before { - top: 0; - margin-top: -12px; - margin-left: -6px; - border-bottom-color: #fff; -} - -.leaflet-tooltip-left { - margin-left: -6px; -} - -.leaflet-tooltip-right { - margin-left: 6px; -} - -.leaflet-tooltip-left:before, -.leaflet-tooltip-right:before { - top: 50%; - margin-top: -6px; -} - -.leaflet-tooltip-left:before { - right: 0; - margin-right: -12px; - border-left-color: #fff; -} - -.leaflet-tooltip-right:before { - left: 0; - margin-left: -12px; - border-right-color: #fff; -} diff --git a/test.ts b/test.ts index 167ce125a7..c2d7915487 100644 --- a/test.ts +++ b/test.ts @@ -1,94 +1,15 @@ import SvelteUIElement from "./UI/Base/SvelteUIElement" -import MaplibreMap from "./UI/Map/MaplibreMap.svelte" -import { UIEventSource } from "./Logic/UIEventSource" -import { MapLibreAdaptor } from "./UI/Map/MapLibreAdaptor" -import { AvailableRasterLayers, RasterLayerPolygon } from "./Models/RasterLayers" -import type { Map as MlMap } from "maplibre-gl" -import { ShowDataLayer } from "./UI/Map/ShowDataLayer" -import LayerConfig from "./Models/ThemeConfig/LayerConfig" -import * as bench from "./assets/generated/layers/bench.json" -import { Utils } from "./Utils" -import SimpleFeatureSource from "./Logic/FeatureSource/Sources/SimpleFeatureSource" -import { FilterState } from "./Models/FilteredLayer" +import ThemeViewGUI from "./UI/ThemeViewGUI.svelte" import { FixedUiElement } from "./UI/Base/FixedUiElement" +import { QueryParameters } from "./Logic/Web/QueryParameters" +import { AllKnownLayoutsLazy } from "./Customizations/AllKnownLayouts" async function main() { - const mlmap = new UIEventSource(undefined) - const location = new UIEventSource<{ lon: number; lat: number }>({ - lat: 51.1, - lon: 3.1, - }) - new SvelteUIElement(MaplibreMap, { - map: mlmap, - }) - .SetClass("border border-black") - .SetStyle("height: 50vh; width: 90%; margin: 1%") - .AttachTo("maindiv") - const bg = new UIEventSource(undefined) - const mla = new MapLibreAdaptor(mlmap, { - rasterLayer: bg, - location, - }) - - const features = new UIEventSource([ - { - feature: { - type: "Feature", - properties: { - hello: "world", - id: "" + 1, - }, - geometry: { - type: "Point", - coordinates: [3.1, 51.2], - }, - }, - freshness: new Date(), - }, - ]) - const layer = new LayerConfig(bench) - const options = { - zoomToFeatures: false, - features: new SimpleFeatureSource( - { - layerDef: layer, - isDisplayed: new UIEventSource(true), - appliedFilters: new UIEventSource>(undefined), - }, - 0, - features - ), - layer, - } - new ShowDataLayer(mlmap, options) - mla.zoom.set(9) - mla.location.set({ lon: 3.1, lat: 51.1 }) - const availableLayers = AvailableRasterLayers.layersAvailableAt(location) - // new BackgroundLayerResetter(bg, availableLayers) - // new SvelteUIElement(RasterLayerPicker, { availableLayers, value: bg }).AttachTo("extradiv") - for (let i = 0; i <= 10; i++) { - await Utils.waitFor(1000) - features.ping() - new FixedUiElement("> " + (5 - i)).AttachTo("extradiv") - } - options.zoomToFeatures = false - features.setData([ - { - feature: { - type: "Feature", - properties: { - hello: "world", - id: "" + 1, - }, - geometry: { - type: "Point", - coordinates: [3.103, 51.10003], - }, - }, - freshness: new Date(), - }, - ]) - new FixedUiElement("> OK").AttachTo("extradiv") + new FixedUiElement("Determining layout...").AttachTo("maindiv") + const qp = QueryParameters.GetQueryParameter("layout", "benches") + const layout = new AllKnownLayoutsLazy().get(qp.data) + console.log("Using layout", layout.id) + new SvelteUIElement(ThemeViewGUI, { layout }).AttachTo("maindiv") } main().then((_) => {}) diff --git a/theme.html b/theme.html index 5828c58e99..50d3fd9cb6 100644 --- a/theme.html +++ b/theme.html @@ -4,7 +4,6 @@ - From 8e2f04c0d0e137928fe634d0dae9b13190f05c58 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 25 Mar 2023 02:48:24 +0100 Subject: [PATCH 005/257] refactoring: split all the states --- Logic/Actors/OverpassFeatureSource.ts | 2 +- Logic/Actors/TitleHandler.ts | 12 +- Logic/BBox.ts | 2 +- Logic/FeatureSource/FeaturePipeline.ts | 40 +--- Logic/State/FeaturePipelineState.ts | 4 +- Logic/State/LayerState.ts | 145 +++++++++++++ Logic/State/MapState.ts | 196 +----------------- Logic/State/UserRelatedState.ts | 28 ++- Models/Constants.ts | 21 +- Models/ThemeConfig/Conversion/PrepareTheme.ts | 4 +- Models/ThemeConfig/Conversion/Validation.ts | 5 +- .../Json/PointRenderingConfigJson.ts | 11 + Models/ThemeConfig/LayerConfig.ts | 50 ++--- Models/ThemeConfig/PointRenderingConfig.ts | 11 + Models/ThemeConfig/SourceConfig.ts | 3 +- UI/BigComponents/DownloadPanel.ts | 2 +- UI/BigComponents/GeolocationControl.ts | 1 - UI/ImportFlow/MapPreview.ts | 2 +- UI/Map/MapLibreAdaptor.ts | 6 +- UI/Map/ShowDataLayer.ts | 130 +++++++++--- UI/ThemeViewGUI.svelte | 64 ++++-- assets/layers/gps_location/gps_location.json | 1 + .../layers/home_location/home_location.json | 2 +- .../import_candidate/import_candidate.json | 2 +- assets/layers/range/range.json | 4 +- assets/layers/split_point/split_point.json | 6 +- assets/layers/type_node/type_node.json | 10 - assets/themes/grb/grb.json | 25 +-- scripts/generateDocs.ts | 6 +- scripts/generateStats.ts | 2 +- scripts/generateTaginfoProjectFiles.ts | 2 +- test.ts | 7 +- 32 files changed, 411 insertions(+), 395 deletions(-) create mode 100644 Logic/State/LayerState.ts delete mode 100644 assets/layers/type_node/type_node.json diff --git a/Logic/Actors/OverpassFeatureSource.ts b/Logic/Actors/OverpassFeatureSource.ts index bd2e9c4b0e..bf8c44464b 100644 --- a/Logic/Actors/OverpassFeatureSource.ts +++ b/Logic/Actors/OverpassFeatureSource.ts @@ -148,7 +148,7 @@ export default class OverpassFeatureSource implements FeatureSource { if (typeof layer === "string") { throw "A layer was not expanded!" } - if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { + if (layer.source === undefined) { continue } if (this.state.locationControl.data.zoom < layer.minzoom) { diff --git a/Logic/Actors/TitleHandler.ts b/Logic/Actors/TitleHandler.ts index 729f172c16..442158374b 100644 --- a/Logic/Actors/TitleHandler.ts +++ b/Logic/Actors/TitleHandler.ts @@ -7,14 +7,9 @@ import { ElementStorage } from "../ElementStorage" import { Utils } from "../../Utils" export default class TitleHandler { - constructor(state: { - selectedElement: Store - layoutToUse: LayoutConfig - allElements: ElementStorage - }) { - const currentTitle: Store = state.selectedElement.map( + constructor(selectedElement: Store, layout: LayoutConfig, allElements: ElementStorage) { + const currentTitle: Store = selectedElement.map( (selected) => { - const layout = state.layoutToUse const defaultTitle = layout?.title?.txt ?? "MapComplete" if (selected === undefined) { @@ -28,8 +23,7 @@ export default class TitleHandler { } if (layer.source.osmTags.matchesProperties(tags)) { const tagsSource = - state.allElements.getEventSourceById(tags.id) ?? - new UIEventSource(tags) + allElements.getEventSourceById(tags.id) ?? new UIEventSource(tags) const title = new TagRenderingAnswer(tagsSource, layer.title, {}) return ( new Combine([defaultTitle, " | ", title]).ConstructElement() diff --git a/Logic/BBox.ts b/Logic/BBox.ts index c23f2073ca..3a875196e2 100644 --- a/Logic/BBox.ts +++ b/Logic/BBox.ts @@ -189,7 +189,7 @@ export class BBox { public asGeoJson(properties: T): Feature { return { type: "Feature", - properties: properties, + properties, geometry: this.asGeometry(), } } diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index edfac8fbdc..2492c0c107 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -50,7 +50,7 @@ export default class FeaturePipeline { public readonly relationTracker: RelationsTracker /** * Keeps track of all raw OSM-nodes. - * Only initialized if 'type_node' is defined as layer + * Only initialized if `ReplaceGeometryAction` is needed somewhere */ public readonly fullNodeDatabase?: FullNodeDatabaseSource private readonly overpassUpdater: OverpassFeatureSource @@ -132,14 +132,6 @@ export default class FeaturePipeline { // We do not mark as visited here, this is the responsability of the code near the actual loader (e.g. overpassLoader and OSMApiFeatureLoader) } - function handlePriviligedFeatureSource(src: FeatureSourceForLayer & Tiled) { - // Passthrough to passed function, except that it registers as well - handleFeatureSource(src) - src.features.addCallbackAndRunD((fs) => { - fs.forEach((ff) => state.allElements.addOrGetElement(ff)) - }) - } - for (const filteredLayer of state.filteredLayers.data) { const id = filteredLayer.layerDef.id const source = filteredLayer.layerDef.source @@ -160,36 +152,6 @@ export default class FeaturePipeline { continue } - if (id === "selected_element") { - handlePriviligedFeatureSource(state.selectedElementsLayer) - continue - } - - if (id === "gps_location") { - handlePriviligedFeatureSource(state.currentUserLocation) - continue - } - - if (id === "gps_location_history") { - handlePriviligedFeatureSource(state.historicalUserLocations) - continue - } - - if (id === "gps_track") { - handlePriviligedFeatureSource(state.historicalUserLocationsTrack) - continue - } - - if (id === "home_location") { - handlePriviligedFeatureSource(state.homeLocation) - continue - } - - if (id === "current_view") { - handlePriviligedFeatureSource(state.currentView) - continue - } - const localTileSaver = new SaveTileToLocalStorageActor(filteredLayer) this.localStorageSavers.set(filteredLayer.layerDef.id, localTileSaver) diff --git a/Logic/State/FeaturePipelineState.ts b/Logic/State/FeaturePipelineState.ts index 5f7fe819d8..429952f379 100644 --- a/Logic/State/FeaturePipelineState.ts +++ b/Logic/State/FeaturePipelineState.ts @@ -14,7 +14,7 @@ import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import ShowDataLayer from "../../UI/Map/ShowDataLayer" -export default class FeaturePipelineState extends MapState { +export default class FeaturePipelineState { /** * The piece of code which fetches data from various sources and shows it on the background map */ @@ -27,8 +27,6 @@ export default class FeaturePipelineState extends MapState { >() constructor(layoutToUse: LayoutConfig) { - super(layoutToUse) - const clustering = layoutToUse?.clustering this.featureAggregator = TileHierarchyAggregator.createHierarchy(this) const clusterCounter = this.featureAggregator diff --git a/Logic/State/LayerState.ts b/Logic/State/LayerState.ts new file mode 100644 index 0000000000..46fd6989c5 --- /dev/null +++ b/Logic/State/LayerState.ts @@ -0,0 +1,145 @@ +import { UIEventSource } from "../UIEventSource" +import { GlobalFilter } from "../../Models/GlobalFilter" +import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" +import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import { OsmConnection } from "../Osm/OsmConnection" +import { LocalStorageSource } from "../Web/LocalStorageSource" +import { QueryParameters } from "../Web/QueryParameters" + +/** + * The layer state keeps track of: + * - Which layers are enabled + * - Which filters are used, including 'global' filters + */ +export default class LayerState { + /** + * Filters which apply onto all layers + */ + public readonly globalFilters: UIEventSource = new UIEventSource( + [], + "globalFilters" + ) + + /** + * Which layers are enabled in the current theme and what filters are applied onto them + */ + public readonly filteredLayers: Map + private readonly osmConnection: OsmConnection + + /** + * + * @param osmConnection + * @param layers + * @param context: the context, probably the name of the theme. Used to disambiguate the upstream user preference + */ + constructor(osmConnection: OsmConnection, layers: LayerConfig[], context: string) { + this.osmConnection = osmConnection + this.filteredLayers = new Map() + for (const layer of layers) { + this.filteredLayers.set(layer.id, this.initFilteredLayer(layer, context)) + } + layers.forEach((l) => this.linkFilterStates(l)) + } + + private static getPref( + osmConnection: OsmConnection, + key: string, + layer: LayerConfig + ): UIEventSource { + return osmConnection.GetPreference(key, layer.shownByDefault + "").sync( + (v) => { + if (v === undefined) { + return undefined + } + return v === "true" + }, + [], + (b) => { + if (b === undefined) { + return undefined + } + return "" + b + } + ) + } + /** + * INitializes a filtered layer for the given layer. + * @param layer + * @param context: probably the theme-name. This is used to disambiguate the user settings; e.g. when using the same layer in different contexts + * @private + */ + private initFilteredLayer(layer: LayerConfig, context: string): FilteredLayer | undefined { + let isDisplayed: UIEventSource + const osmConnection = this.osmConnection + if (layer.syncSelection === "local") { + isDisplayed = LocalStorageSource.GetParsed( + context + "-layer-" + layer.id + "-enabled", + layer.shownByDefault + ) + } else if (layer.syncSelection === "theme-only") { + isDisplayed = LayerState.getPref( + osmConnection, + context + "-layer-" + layer.id + "-enabled", + layer + ) + } else if (layer.syncSelection === "global") { + isDisplayed = LayerState.getPref(osmConnection, "layer-" + layer.id + "-enabled", layer) + } else { + isDisplayed = QueryParameters.GetBooleanQueryParameter( + "layer-" + layer.id, + layer.shownByDefault, + "Wether or not layer " + layer.id + " is shown" + ) + } + + const flayer: FilteredLayer = { + isDisplayed, + layerDef: layer, + appliedFilters: new UIEventSource>( + new Map() + ), + } + layer.filters?.forEach((filterConfig) => { + const stateSrc = filterConfig.initState() + + stateSrc.addCallbackAndRun((state) => + flayer.appliedFilters.data.set(filterConfig.id, state) + ) + flayer.appliedFilters + .map((dict) => dict.get(filterConfig.id)) + .addCallback((state) => stateSrc.setData(state)) + }) + + return flayer + } + + /** + * Some layers copy the filter state of another layer - this is quite often the case for 'sibling'-layers, + * (where two variations of the same layer are used, e.g. a specific type of shop on all zoom levels and all shops on high zoom). + * + * This methods links those states for the given layer + */ + private linkFilterStates(layer: LayerConfig) { + if (layer.filterIsSameAs === undefined) { + return + } + const toReuse = this.filteredLayers.get(layer.filterIsSameAs) + if (toReuse === undefined) { + throw ( + "Error in layer " + + layer.id + + ": it defines that it should be use the filters of " + + layer.filterIsSameAs + + ", but this layer was not loaded" + ) + } + console.warn( + "Linking filter and isDisplayed-states of " + layer.id + " and " + layer.filterIsSameAs + ) + this.filteredLayers.set(layer.id, { + isDisplayed: toReuse.isDisplayed, + layerDef: layer, + appliedFilters: toReuse.appliedFilters, + }) + } +} diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts index ad29cfaa6f..0e695d356a 100644 --- a/Logic/State/MapState.ts +++ b/Logic/State/MapState.ts @@ -1,31 +1,19 @@ import { Store, UIEventSource } from "../UIEventSource" -import Attribution from "../../UI/BigComponents/Attribution" -import BaseUIElement from "../../UI/BaseUIElement" -import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" +import FilteredLayer from "../../Models/FilteredLayer" import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" import { QueryParameters } from "../Web/QueryParameters" import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer" -import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource" -import { LocalStorageSource } from "../Web/LocalStorageSource" -import TitleHandler from "../Actors/TitleHandler" -import { BBox } from "../BBox" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import FeatureSource, { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource" import StaticFeatureSource, { TiledStaticFeatureSource, } from "../FeatureSource/Sources/StaticFeatureSource" -import { OsmConnection } from "../Osm/OsmConnection" import { Feature } from "geojson" -import { Map as MlMap } from "maplibre-gl" -import { GlobalFilter } from "../../Models/GlobalFilter" import { MapProperties } from "../../Models/MapProperties" -import ShowDataLayer from "../../UI/Map/ShowDataLayer" /** * Contains all the leaflet-map related state */ export default class MapState { - - /** * Last location where a click was registered */ @@ -46,21 +34,6 @@ export default class MapState { */ public selectedElementsLayer: FeatureSourceForLayer & Tiled - public readonly mainMapObject: BaseUIElement - - /** - * Which layers are enabled in the current theme and what filters are applied onto them - */ - public filteredLayers: UIEventSource = new UIEventSource( - [], - "filteredLayers" - ) - - /** - * Filters which apply onto all layers - */ - public globalFilters: UIEventSource = new UIEventSource([], "globalFilters") - /** * Which overlays are shown */ @@ -80,16 +53,6 @@ export default class MapState { this.backgroundLayer = new UIEventSource(defaultLayer) this.backgroundLayer.addCallbackAndRunD((layer) => self.backgroundLayerId.setData(layer.id)) - // Will write into this.leafletMap - this.mainMapObject = Minimap.createMiniMap({ - background: this.backgroundLayer, - location: this.locationControl, - leafletMap: this.leafletMap, - bounds: this.currentBounds, - attribution: attr, - lastClickLocation: this.LastClickLocation, - }) - this.overlayToggles = this.layoutToUse?.tileLayerSources ?.filter((c) => c.name !== undefined) @@ -101,16 +64,11 @@ export default class MapState { "Wether or not the overlay " + c.id + " is shown" ), })) ?? [] - this.filteredLayers = new UIEventSource( - MapState.InitializeFilteredLayers(this.layoutToUse, this.osmConnection) - ) this.AddAllOverlaysToMap(this.leafletMap) this.initCurrentView() this.initSelectedElement() - - new TitleHandler(this) } public AddAllOverlaysToMap(leafletMap: UIEventSource) { @@ -128,48 +86,23 @@ export default class MapState { } } - - private initCurrentView() { - let currentViewLayer: FilteredLayer = this.filteredLayers.data.filter( - (l) => l.layerDef.id === "current_view" - )[0] - - if (currentViewLayer === undefined) { - // This layer is not needed by the theme and thus unloaded - return - } - + private static initCurrentView(mapproperties: MapProperties): FeatureSource { let i = 0 - const self = this - const features: Store = this.currentBounds.map((bounds) => { + const features: Store = mapproperties.bounds.map((bounds) => { if (bounds === undefined) { return [] } i++ - const feature = { - type: "Feature", - properties: { + return [ + bounds.asGeoJson({ id: "current_view-" + i, current_view: "yes", - zoom: "" + self.locationControl.data.zoom, - }, - geometry: { - type: "Polygon", - coordinates: [ - [ - [bounds.maxLon, bounds.maxLat], - [bounds.minLon, bounds.maxLat], - [bounds.minLon, bounds.minLat], - [bounds.maxLon, bounds.minLat], - [bounds.maxLon, bounds.maxLat], - ], - ], - }, - } - return [feature] + zoom: "" + mapproperties.zoom.data, + }), + ] }) - this.currentView = new TiledStaticFeatureSource(features, currentViewLayer) + return new StaticFeatureSource(features) } private initSelectedElement() { @@ -197,113 +130,4 @@ export default class MapState { }) this.selectedElementsLayer = new TiledStaticFeatureSource(store, layerDef) } - - private static getPref( - osmConnection: OsmConnection, - key: string, - layer: LayerConfig - ): UIEventSource { - return osmConnection.GetPreference(key, layer.shownByDefault + "").sync( - (v) => { - if (v === undefined) { - return undefined - } - return v === "true" - }, - [], - (b) => { - if (b === undefined) { - return undefined - } - return "" + b - } - ) - } - - public static InitializeFilteredLayers( - layoutToUse: { layers: LayerConfig[]; id: string }, - osmConnection: OsmConnection - ): FilteredLayer[] { - if (layoutToUse === undefined) { - return [] - } - const flayers: FilteredLayer[] = [] - for (const layer of layoutToUse.layers) { - let isDisplayed: UIEventSource - if (layer.syncSelection === "local") { - isDisplayed = LocalStorageSource.GetParsed( - layoutToUse.id + "-layer-" + layer.id + "-enabled", - layer.shownByDefault - ) - } else if (layer.syncSelection === "theme-only") { - isDisplayed = MapState.getPref( - osmConnection, - layoutToUse.id + "-layer-" + layer.id + "-enabled", - layer - ) - } else if (layer.syncSelection === "global") { - isDisplayed = MapState.getPref( - osmConnection, - "layer-" + layer.id + "-enabled", - layer - ) - } else { - isDisplayed = QueryParameters.GetBooleanQueryParameter( - "layer-" + layer.id, - layer.shownByDefault, - "Wether or not layer " + layer.id + " is shown" - ) - } - - const flayer: FilteredLayer = { - isDisplayed, - layerDef: layer, - appliedFilters: new UIEventSource>( - new Map() - ), - } - layer.filters.forEach((filterConfig) => { - const stateSrc = filterConfig.initState() - - stateSrc.addCallbackAndRun((state) => - flayer.appliedFilters.data.set(filterConfig.id, state) - ) - flayer.appliedFilters - .map((dict) => dict.get(filterConfig.id)) - .addCallback((state) => stateSrc.setData(state)) - }) - - flayers.push(flayer) - } - - for (const layer of layoutToUse.layers) { - if (layer.filterIsSameAs === undefined) { - continue - } - const toReuse = flayers.find((l) => l.layerDef.id === layer.filterIsSameAs) - if (toReuse === undefined) { - throw ( - "Error in layer " + - layer.id + - ": it defines that it should be use the filters of " + - layer.filterIsSameAs + - ", but this layer was not loaded" - ) - } - console.warn( - "Linking filter and isDisplayed-states of " + - layer.id + - " and " + - layer.filterIsSameAs - ) - const selfLayer = flayers.findIndex((l) => l.layerDef.id === layer.id) - flayers[selfLayer] = { - isDisplayed: toReuse.isDisplayed, - layerDef: layer, - appliedFilters: toReuse.appliedFilters, - } - } - - return flayers - } } diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index c178aa984f..9f6f115a68 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -6,6 +6,7 @@ import Locale from "../../UI/i18n/Locale" import { Changes } from "../Osm/Changes" import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" import FeatureSource from "../FeatureSource/FeatureSource" +import { Feature } from "geojson" /** * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, @@ -182,7 +183,7 @@ export default class UserRelatedState { private initHomeLocation(): FeatureSource { const empty = [] - const feature = Stores.ListStabilized( + const feature: Store = Stores.ListStabilized( this.osmConnection.userDetails.map((userDetails) => { if (userDetails === undefined) { return undefined @@ -198,21 +199,18 @@ export default class UserRelatedState { return empty } return [ - { - feature: { - type: "Feature", - properties: { - id: "home", - "user:home": "yes", - _lon: homeLonLat[0], - _lat: homeLonLat[1], - }, - geometry: { - type: "Point", - coordinates: homeLonLat, - }, + { + type: "Feature", + properties: { + id: "home", + "user:home": "yes", + _lon: homeLonLat[0], + _lat: homeLonLat[1], + }, + geometry: { + type: "Point", + coordinates: homeLonLat, }, - freshness: new Date(), }, ] }) diff --git a/Models/Constants.ts b/Models/Constants.ts index fe4f4f594d..8aeb95f969 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -26,31 +26,30 @@ export default class Constants { // Doesn't support nwr: "https://overpass.openstreetmap.fr/api/interpreter" ] - public static readonly added_by_default: string[] = [ + public static readonly added_by_default = [ "selected_element", "gps_location", "gps_location_history", "home_location", "gps_track", - ] - public static readonly no_include: string[] = [ + "range", + ] as const + /** + * Special layers which are not included in a theme by default + */ + public static readonly no_include = [ "conflation", - "left_right_style", "split_point", "current_view", "matchpoint", - ] + ] as const /** * Layer IDs of layers which have special properties through built-in hooks */ - public static readonly priviliged_layers: string[] = [ + public static readonly priviliged_layers = [ ...Constants.added_by_default, - "type_node", - "note", - "import_candidate", - "direction", ...Constants.no_include, - ] + ] as const // The user journey states thresholds when a new feature gets unlocked public static userJourney = { diff --git a/Models/ThemeConfig/Conversion/PrepareTheme.ts b/Models/ThemeConfig/Conversion/PrepareTheme.ts index 15e41503f4..ff9745db8c 100644 --- a/Models/ThemeConfig/Conversion/PrepareTheme.ts +++ b/Models/ThemeConfig/Conversion/PrepareTheme.ts @@ -255,7 +255,7 @@ class AddImportLayers extends DesugaringStep { const creator = new CreateNoteImportLayer() for (let i1 = 0; i1 < allLayers.length; i1++) { const layer = allLayers[i1] - if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { + if (layer.source === undefined) { // Priviliged layers are skipped continue } @@ -600,7 +600,7 @@ class PreparePersonalTheme extends DesugaringStep { // All other preparations are done by the 'override-all'-block in personal.json json.layers = Array.from(this._state.sharedLayers.keys()) - .filter((l) => Constants.priviliged_layers.indexOf(l) < 0) + .filter((l) => this._state.sharedLayers.get(l).source !== null) .filter((l) => this._state.publicLayers.has(l)) return { result: json, diff --git a/Models/ThemeConfig/Conversion/Validation.ts b/Models/ThemeConfig/Conversion/Validation.ts index 765b095d68..5c31778e6c 100644 --- a/Models/ThemeConfig/Conversion/Validation.ts +++ b/Models/ThemeConfig/Conversion/Validation.ts @@ -845,7 +845,7 @@ export class ValidateLayer extends DesugaringStep { } if (json.description === undefined) { - if (Constants.priviliged_layers.indexOf(json.id) >= 0) { + if (typeof json.source === null) { errors.push(context + ": A priviliged layer must have a description") } else { warnings.push(context + ": A builtin layer should have a description") @@ -882,6 +882,9 @@ export class ValidateLayer extends DesugaringStep { } if (json.presets !== undefined) { + if (typeof json.source === "string") { + throw "A special layer cannot have presets" + } // Check that a preset will be picked up by the layer itself const baseTags = TagUtils.Tag(json.source.osmTags) for (let i = 0; i < json.presets.length; i++) { diff --git a/Models/ThemeConfig/Json/PointRenderingConfigJson.ts b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts index 17e243924e..6741b822c4 100644 --- a/Models/ThemeConfig/Json/PointRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts @@ -77,4 +77,15 @@ export default interface PointRenderingConfigJson { * A snippet of css-classes. They can be space-separated */ cssClasses?: string | TagRenderingConfigJson + + /** + * If the map is pitched, the marker will stay parallel to the screen. + * Set to 'map' if you want to put it flattened on the map + */ + pitchAlignment?: "canvas" | "map" | TagRenderingConfigJson + + /** + * If the map is rotated, the icon will still point to the north if no rotation was applied + */ + rotationAlignment?: "map" | "canvas" | TagRenderingConfigJson } diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index 03be4abee6..599aa3fb31 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -86,9 +86,9 @@ export default class LayerConfig extends WithContextLoader { throw "Layer " + this.id + " does not define a source section (" + context + ")" } - if(json.source === "special" || json.source === "special:library"){ + if (json.source === "special" || json.source === "special:library") { this.source = null - }else if (json.source.osmTags === undefined) { + } else if (json.source.osmTags === undefined) { throw ( "Layer " + this.id + @@ -105,7 +105,6 @@ export default class LayerConfig extends WithContextLoader { throw `${context}: The id of a layer should match [a-z0-9-_]*: ${json.id}` } - this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30 if ( json.syncSelection !== undefined && LayerConfig.syncSelectionAllowed.indexOf(json.syncSelection) < 0 @@ -120,13 +119,28 @@ export default class LayerConfig extends WithContextLoader { ) } this.syncSelection = json.syncSelection ?? "no" - const osmTags = TagUtils.Tag(json.source.osmTags, context + "source.osmTags") + if (typeof json.source !== "string") { + this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30 + const osmTags = TagUtils.Tag(json.source.osmTags, context + "source.osmTags") + if (osmTags.isNegative()) { + throw ( + context + + "The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" + + osmTags.asHumanString(false, false, {}) + ) + } - if (Constants.priviliged_layers.indexOf(this.id) < 0 && osmTags.isNegative()) { - throw ( - context + - "The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" + - osmTags.asHumanString(false, false, {}) + this.source = new SourceConfig( + { + osmTags: osmTags, + geojsonSource: json.source["geoJson"], + geojsonSourceLevel: json.source["geoJsonZoomLevel"], + overpassScript: json.source["overpassScript"], + isOsmCache: json.source["isOsmCache"], + mercatorCrs: json.source["mercatorCrs"], + idKey: json.source["idKey"], + }, + json.id ) } @@ -138,20 +152,6 @@ export default class LayerConfig extends WithContextLoader { throw context + "Use 'geoJson' instead of 'geojson' (the J is a capital letter)" } - this.source = new SourceConfig( - { - osmTags: osmTags, - geojsonSource: json.source["geoJson"], - geojsonSourceLevel: json.source["geoJsonZoomLevel"], - overpassScript: json.source["overpassScript"], - isOsmCache: json.source["isOsmCache"], - mercatorCrs: json.source["mercatorCrs"], - idKey: json.source["idKey"], - }, - Constants.priviliged_layers.indexOf(this.id) > 0, - json.id - ) - this.allowSplit = json.allowSplit ?? false this.name = Translations.T(json.name, translationContext + ".name") if (json.units !== undefined && !Array.isArray(json.units)) { @@ -250,7 +250,7 @@ export default class LayerConfig extends WithContextLoader { | "osmbasedmap" | "historicphoto" | string - )[] + )[] if (typeof pr.preciseInput.preferredBackground === "string") { preferredBackground = [pr.preciseInput.preferredBackground] } else { @@ -597,7 +597,7 @@ export default class LayerConfig extends WithContextLoader { } let overpassLink: BaseUIElement = undefined - if (Constants.priviliged_layers.indexOf(this.id) < 0) { + if (this.source !== undefined) { try { overpassLink = new Link( "Execute on overpass", diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts index 9e0fdd5590..e8a467bf83 100644 --- a/Models/ThemeConfig/PointRenderingConfig.ts +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -12,6 +12,7 @@ import Img from "../../UI/Base/Img" import Combine from "../../UI/Base/Combine" import { VariableUiElement } from "../../UI/Base/VariableUIElement" import { OsmTags } from "../OsmFeature" +import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" export default class PointRenderingConfig extends WithContextLoader { private static readonly allowed_location_codes = new Set([ @@ -32,6 +33,8 @@ export default class PointRenderingConfig extends WithContextLoader { public readonly rotation: TagRenderingConfig public readonly cssDef: TagRenderingConfig public readonly cssClasses?: TagRenderingConfig + public readonly pitchAlignment?: TagRenderingConfig + public readonly rotationAlignment?: TagRenderingConfig constructor(json: PointRenderingConfigJson, context: string) { super(json, context) @@ -88,6 +91,14 @@ export default class PointRenderingConfig extends WithContextLoader { this.iconSize = this.tr("iconSize", "40,40,center") this.label = this.tr("label", undefined) this.rotation = this.tr("rotation", "0") + if (json.pitchAlignment) { + console.log("Got a pitch alignment!", json.pitchAlignment) + } + this.pitchAlignment = this.tr("pitchAlignment", "canvas") + this.rotationAlignment = this.tr( + "rotationAlignment", + json.pitchAlignment === "map" ? "map" : "canvas" + ) } /** diff --git a/Models/ThemeConfig/SourceConfig.ts b/Models/ThemeConfig/SourceConfig.ts index 1badbc57b6..32fb0333b8 100644 --- a/Models/ThemeConfig/SourceConfig.ts +++ b/Models/ThemeConfig/SourceConfig.ts @@ -20,7 +20,6 @@ export default class SourceConfig { geojsonSourceLevel?: number idKey?: string }, - isSpecialLayer: boolean, context?: string ) { let defined = 0 @@ -51,7 +50,7 @@ export default class SourceConfig { throw `Source defines a geojson-zoomLevel, but does not specify {x} nor {y} (or equivalent), this is probably a bug (in context ${context})` } } - if (params.osmTags !== undefined && !isSpecialLayer) { + if (params.osmTags !== undefined) { const optimized = params.osmTags.optimize() if (optimized === false) { throw ( diff --git a/UI/BigComponents/DownloadPanel.ts b/UI/BigComponents/DownloadPanel.ts index a2685016a4..d07f95d255 100644 --- a/UI/BigComponents/DownloadPanel.ts +++ b/UI/BigComponents/DownloadPanel.ts @@ -228,7 +228,7 @@ export class DownloadPanel extends Toggle { new Set(neededLayers) ) for (const tile of featureList) { - if (Constants.priviliged_layers.indexOf(tile.layer) >= 0) { + if (tile.layer !== undefined) { continue } diff --git a/UI/BigComponents/GeolocationControl.ts b/UI/BigComponents/GeolocationControl.ts index ee5465b12d..f1aea540c6 100644 --- a/UI/BigComponents/GeolocationControl.ts +++ b/UI/BigComponents/GeolocationControl.ts @@ -31,7 +31,6 @@ export class GeolocationControl extends VariableUiElement { return false } const timeDiff = (new Date().getTime() - date.getTime()) / 1000 - console.log("Timediff", timeDiff) return timeDiff <= Constants.zoomToLocationTimeout } ) diff --git a/UI/ImportFlow/MapPreview.ts b/UI/ImportFlow/MapPreview.ts index c000c7a68a..ce8c2ae513 100644 --- a/UI/ImportFlow/MapPreview.ts +++ b/UI/ImportFlow/MapPreview.ts @@ -59,7 +59,7 @@ export class MapPreview } const availableLayers = AllKnownLayouts.AllPublicLayers().filter( - (l) => l.name !== undefined && Constants.priviliged_layers.indexOf(l.id) < 0 + (l) => l.name !== undefined && l.source !== undefined ) const layerPicker = new DropDown( t.selectLayer, diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index 3b9aba3ac3..2b2f4053f1 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -14,12 +14,12 @@ import Constants from "../../Models/Constants" */ export class MapLibreAdaptor implements MapProperties { private static maplibre_control_handlers = [ - "scrollZoom", - "boxZoom", + // "scrollZoom", + // "boxZoom", + // "doubleClickZoom", "dragRotate", "dragPan", "keyboard", - "doubleClickZoom", "touchZoomRotate", ] readonly location: UIEventSource<{ lon: number; lat: number }> diff --git a/UI/Map/ShowDataLayer.ts b/UI/Map/ShowDataLayer.ts index c5e1ee15a5..2f2d820a79 100644 --- a/UI/Map/ShowDataLayer.ts +++ b/UI/Map/ShowDataLayer.ts @@ -8,12 +8,13 @@ import PointRenderingConfig from "../../Models/ThemeConfig/PointRenderingConfig" import { OsmTags } from "../../Models/OsmFeature" import FeatureSource from "../../Logic/FeatureSource/FeatureSource" import { BBox } from "../../Logic/BBox" -import { Feature, LineString } from "geojson" +import { Feature } from "geojson" import ScrollableFullScreen from "../Base/ScrollableFullScreen" import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" import { Utils } from "../../Utils" import * as range_layer from "../../assets/layers/range/range.json" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" + class PointRenderingLayer { private readonly _config: PointRenderingConfig private readonly _fetchStore?: (id: string) => Store @@ -44,6 +45,16 @@ class PointRenderingLayer { const unseenKeys = new Set(cache.keys()) for (const location of this._config.location) { for (const feature of features) { + if (feature?.geometry === undefined) { + console.warn( + "Got an invalid feature:", + features, + " while rendering", + location, + "of", + this._config + ) + } const loc = GeoOperations.featureToCoordinateWithRenderingType( feature, location @@ -102,7 +113,14 @@ class PointRenderingLayer { }) } - return new Marker(el).setLngLat(loc).setOffset(iconAnchor).addTo(this._map) + const marker = new Marker(el).setLngLat(loc).setOffset(iconAnchor).addTo(this._map) + store + .map((tags) => this._config.pitchAlignment.GetRenderValue(tags).Subs(tags).txt) + .addCallbackAndRun((pitchAligment) => marker.setPitchAlignment(pitchAligment)) + store + .map((tags) => this._config.rotationAlignment.GetRenderValue(tags).Subs(tags).txt) + .addCallbackAndRun((pitchAligment) => marker.setRotationAlignment(pitchAligment)) + return marker } } @@ -118,13 +136,17 @@ class LineRenderingLayer { "offset", "fill", "fillColor", - ] + ] as const + + private static readonly lineConfigKeysColor = ["color", "fillColor"] as const + private static readonly lineConfigKeysNumber = ["width", "offset"] as const private readonly _map: MlMap private readonly _config: LineRenderingConfig private readonly _visibility?: Store private readonly _fetchStore?: (id: string) => Store private readonly _onClick?: (id: string) => void private readonly _layername: string + private readonly _listenerInstalledOn: Set = new Set() constructor( map: MlMap, @@ -145,6 +167,39 @@ class LineRenderingLayer { features.features.addCallbackAndRunD((features) => self.update(features)) } + private calculatePropsFor( + properties: Record + ): Partial> { + const calculatedProps = {} + const config = this._config + + for (const key of LineRenderingLayer.lineConfigKeys) { + const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt + calculatedProps[key] = v + } + for (const key of LineRenderingLayer.lineConfigKeysColor) { + let v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt + if (v === undefined) { + continue + } + console.log("Color", v) + if (v.length == 9 && v.startsWith("#")) { + // This includes opacity + calculatedProps[key + "-opacity"] = parseInt(v.substring(7), 16) / 256 + v = v.substring(0, 7) + console.log("Color >", v, calculatedProps[key + "-opacity"]) + } + calculatedProps[key] = v + } + for (const key of LineRenderingLayer.lineConfigKeysNumber) { + const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt + calculatedProps[key] = Number(v) + } + + console.log("Calculated props:", calculatedProps, "for", properties.id) + return calculatedProps + } + private async update(features: Feature[]) { const map = this._map while (!map.isStyleLoaded()) { @@ -158,31 +213,14 @@ class LineRenderingLayer { }, promoteId: "id", }) - for (let i = 0; i < features.length; i++) { - const feature = features[i] - const id = feature.properties.id ?? "" + i - const tags = this._fetchStore(id) - tags.addCallbackAndRunD((properties) => { - const config = this._config - - const calculatedProps = {} - for (const key of LineRenderingLayer.lineConfigKeys) { - const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt - calculatedProps[key] = v - } - - map.setFeatureState({ source: this._layername, id }, calculatedProps) - }) - } map.addLayer({ source: this._layername, id: this._layername + "_line", type: "line", - filter: ["in", ["geometry-type"], ["literal", ["LineString", "MultiLineString"]]], - layout: {}, paint: { "line-color": ["feature-state", "color"], + "line-opacity": ["feature-state", "color-opacity"], "line-width": ["feature-state", "width"], "line-offset": ["feature-state", "offset"], }, @@ -205,12 +243,49 @@ class LineRenderingLayer { layout: {}, paint: { "fill-color": ["feature-state", "fillColor"], + "fill-opacity": 0.1, }, }) + + for (let i = 0; i < features.length; i++) { + const feature = features[i] + const id = feature.properties.id ?? feature.id + console.log("ID is", id) + if (id === undefined) { + console.trace( + "Got a feature without ID; this causes rendering bugs:", + feature, + "from" + ) + continue + } + if (this._listenerInstalledOn.has(id)) { + continue + } + if (this._fetchStore === undefined) { + map.setFeatureState( + { source: this._layername, id }, + this.calculatePropsFor(feature.properties) + ) + } else { + const tags = this._fetchStore(id) + this._listenerInstalledOn.add(id) + tags.addCallbackAndRunD((properties) => { + map.setFeatureState( + { source: this._layername, id }, + this.calculatePropsFor(properties) + ) + }) + } + } } } export default class ShowDataLayer { + private static rangeLayer = new LayerConfig( + range_layer, + "ShowDataLayer.ts:range.json" + ) private readonly _map: Store private readonly _options: ShowDataLayerOptions & { layer: LayerConfig } private readonly _popupCache: Map @@ -223,11 +298,6 @@ export default class ShowDataLayer { map.addCallbackAndRunD((map) => self.initDrawFeatures(map)) } - private static rangeLayer = new LayerConfig( - range_layer, - "ShowDataLayer.ts:range.json" - ) - public static showRange( map: Store, features: FeatureSource, @@ -241,6 +311,9 @@ export default class ShowDataLayer { } private openOrReusePopup(id: string): void { + if (!this._popupCache || !this._options.fetchStore) { + return + } if (this._popupCache.has(id)) { this._popupCache.get(id).Activate() return @@ -267,11 +340,12 @@ export default class ShowDataLayer { private initDrawFeatures(map: MlMap) { const { features, doShowLayer, fetchStore, buildPopup } = this._options const onClick = buildPopup === undefined ? undefined : (id) => this.openOrReusePopup(id) - for (const lineRenderingConfig of this._options.layer.lineRendering) { + for (let i = 0; i < this._options.layer.lineRendering.length; i++) { + const lineRenderingConfig = this._options.layer.lineRendering[i] new LineRenderingLayer( map, features, - "test", + this._options.layer.id + "_linerendering_" + i, lineRenderingConfig, doShowLayer, fetchStore, diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 6ef909c514..d2dcd750c9 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -21,11 +21,13 @@ import Svg from "../Svg"; import If from "./Base/If.svelte"; import { GeolocationControl } from "./BigComponents/GeolocationControl.js"; - import FeaturePipeline from "../Logic/FeatureSource/FeaturePipeline"; import { BBox } from "../Logic/BBox"; import ShowDataLayer from "./Map/ShowDataLayer"; import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"; - + import type FeatureSource from "../Logic/FeatureSource/FeatureSource"; + import LayerState from "../Logic/State/LayerState"; + import Constants from "../Models/Constants"; + import type { Feature } from "geojson"; export let layout: LayoutConfig; const maplibremap: UIEventSource = new UIEventSource(undefined); @@ -46,7 +48,7 @@ osmConfiguration: <"osm" | "osm-test">featureSwitches.featureSwitchApiURL.data }); const userRelatedState = new UserRelatedState(osmConnection, layout?.language); - const selectedElement = new UIEventSource(undefined, "Selected element"); + const selectedElement = new UIEventSource(undefined, "Selected element"); const geolocation = new GeoLocationHandler(geolocationState, selectedElement, mapproperties, userRelatedState.gpsLocationHistoryRetentionTime); const allElements = new ElementStorage(); @@ -55,16 +57,19 @@ osmConnection, historicalUserLocations: geolocation.historicalUserLocations }, layout?.isLeftRightSensitive() ?? false); - - Map - + console.log("Setting up layerstate...") + const layerState = new LayerState(osmConnection, layout.layers, layout.id) { // Various actors that we don't need to reference + // TODO enable new TitleHandler(selectedElement,layout,allElements) new ChangeToElementsActor(changes, allElements); new PendingChangesUploader(changes, selectedElement); new SelectedElementTagsUpdater({ allElements, changes, selectedElement, layoutToUse: layout, osmConnection }); + + + // Various initial setup userRelatedState.markLayoutAsVisited(layout); if(layout?.lockLocation){ @@ -76,7 +81,37 @@ featureSwitches.featureSwitchIsTesting ) } - + + + type AddedByDefaultTypes = typeof Constants.added_by_default[number] + /** + * A listing which maps the layerId onto the featureSource + */ + const empty = [] + const specialLayers : Record = { + "home_location": userRelatedState.homeLocation, + gps_location: geolocation.currentUserLocation, + gps_location_history: geolocation.historicalUserLocations, + gps_track: geolocation.historicalUserLocationsTrack, + selected_element: new StaticFeatureSource(selectedElement.map(f => f === undefined ? empty : [f])), + range: new StaticFeatureSource(mapproperties.maxbounds.map(bbox => bbox === undefined ? empty : [bbox.asGeoJson({id:"range"})])) , + current_view: new StaticFeatureSource(mapproperties.bounds.map(bbox => bbox === undefined ? empty : [bbox.asGeoJson({id:"current_view"})])), + } + layerState.filteredLayers.get("range")?.isDisplayed?.syncWith(featureSwitches.featureSwitchIsTesting, true) +console.log("RAnge fs", specialLayers.range) + specialLayers.range.features.addCallbackAndRun(fs => console.log("Range.features:", JSON.stringify(fs))) + layerState.filteredLayers.forEach((flayer) => { + const features = specialLayers[flayer.layerDef.id] + if(features === undefined){ + return + } + new ShowDataLayer(maplibremap, { + features, + doShowLayer: flayer.isDisplayed, + layer: flayer.layerDef, + selectedElement + }) + }) } @@ -93,15 +128,12 @@
- - - mapproperties.zoom.update(z => z+1)}> - - - mapproperties.zoom.update(z => z-1)}> - - - + mapproperties.zoom.update(z => z+1)}> + + + mapproperties.zoom.update(z => z-1)}> + + new GeolocationControl(geolocation, mapproperties).SetClass("block w-8 h-8")}> diff --git a/assets/layers/gps_location/gps_location.json b/assets/layers/gps_location/gps_location.json index a5beb0913d..44a028e913 100644 --- a/assets/layers/gps_location/gps_location.json +++ b/assets/layers/gps_location/gps_location.json @@ -15,6 +15,7 @@ ] }, "iconSize": "40,40,center", + "pitchAlignment": "map", "rotation": { "render": "0deg", "mappings": [ diff --git a/assets/layers/home_location/home_location.json b/assets/layers/home_location/home_location.json index b9fc29509b..75a7e704c6 100644 --- a/assets/layers/home_location/home_location.json +++ b/assets/layers/home_location/home_location.json @@ -2,7 +2,7 @@ "id": "home_location", "description": "Meta layer showing the home location of the user. The home location can be set in the [profile settings](https://www.openstreetmap.org/profile/edit) of OpenStreetMap.", "minzoom": 0, - "source":"special", + "source": "special", "mapRendering": [ { "icon": { diff --git a/assets/layers/import_candidate/import_candidate.json b/assets/layers/import_candidate/import_candidate.json index 52ed5ba3b9..63059ad4bc 100644 --- a/assets/layers/import_candidate/import_candidate.json +++ b/assets/layers/import_candidate/import_candidate.json @@ -1,6 +1,6 @@ { "id": "import_candidate", - "description": "Layer used in the importHelper", + "description": "Layer used as template in the importHelper", "source":"special", "mapRendering": [ { diff --git a/assets/layers/range/range.json b/assets/layers/range/range.json index 1ea7aa4590..a68b56ba05 100644 --- a/assets/layers/range/range.json +++ b/assets/layers/range/range.json @@ -6,9 +6,9 @@ "name": null, "mapRendering": [ { - "width": 4, + "width": 3, "fill": "no", - "color": "#ff000088" + "color": "#cc00cc" } ] } diff --git a/assets/layers/split_point/split_point.json b/assets/layers/split_point/split_point.json index 52d9a22a50..9202df71ca 100644 --- a/assets/layers/split_point/split_point.json +++ b/assets/layers/split_point/split_point.json @@ -2,9 +2,7 @@ "id": "split_point", "description": "Layer rendering the little scissors for the minimap in the 'splitRoadWizard'", "minzoom": 1, - "source": { - "osmTags": "_split_point=yes" - }, + "source": "special", "name": "Split point", "title": "Split point", "mapRendering": [ @@ -17,4 +15,4 @@ "iconSize": "30,30,center" } ] -} \ No newline at end of file +} diff --git a/assets/layers/type_node/type_node.json b/assets/layers/type_node/type_node.json deleted file mode 100644 index 20679e2d73..0000000000 --- a/assets/layers/type_node/type_node.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "id": "type_node", - "description": "This is a priviliged meta_layer which exports _every_ point in OSM. This only works if zoomed below the point that the full tile is loaded (and not loaded via Overpass). Note that this point will also contain a property `parent_ways` which contains all the ways this node is part of as a list. This is mainly used for extremely specialized themes, which do advanced conflations. Expert use only.", - "minzoom": 18, - "source": "special", - "mapRendering": null, - "name": "All OSM Nodes", - "title": "OSM node {id}", - "tagRendering": [] -} diff --git a/assets/themes/grb/grb.json b/assets/themes/grb/grb.json index 91083c2bfb..4d87ff694d 100644 --- a/assets/themes/grb/grb.json +++ b/assets/themes/grb/grb.json @@ -28,29 +28,6 @@ "minzoom": 19 }, "layers": [ - { - "builtin": "type_node", - "override": { - "calculatedTags": [ - "_is_part_of_building=feat.get('parent_ways')?.some(p => p.building !== undefined && p.building !== '') ?? false", - "_is_part_of_grb_building=feat.get('parent_ways')?.some(p => p['source:geometry:ref'] !== undefined) ?? false", - "_is_part_of_building_passage=feat.get('parent_ways')?.some(p => p.tunnel === 'building_passage') ?? false", - "_is_part_of_highway=!feat.get('is_part_of_building_passage') && (feat.get('parent_ways')?.some(p => p.highway !== undefined && p.highway !== '') ?? false)", - "_is_part_of_landuse=feat.get('parent_ways')?.some(p => (p.landuse !== undefined && p.landuse !== '') || (p.natural !== undefined && p.natural !== '')) ?? false", - "_moveable=feat.get('_is_part_of_building') && !feat.get('_is_part_of_grb_building')" - ], - "mapRendering": [ - { - "icon": "square:#cc0", - "iconSize": "5,5,center", - "location": [ - "point" - ] - } - ], - "passAllFeatures": true - } - }, { "id": "osm-buildings", "name": "All OSM-buildings", @@ -771,4 +748,4 @@ "overpassMaxZoom": 17, "osmApiTileSize": 17, "credits": "Pieter Vander Vennet" -} \ No newline at end of file +} diff --git a/scripts/generateDocs.ts b/scripts/generateDocs.ts index 94f80a5e2f..0399c4bf47 100644 --- a/scripts/generateDocs.ts +++ b/scripts/generateDocs.ts @@ -114,7 +114,7 @@ function GenLayerOverviewText(): BaseUIElement { } const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter( - (layer) => Constants.priviliged_layers.indexOf(layer.id) < 0 + (layer) => layer.source === null ) const builtinLayerIds: Set = new Set() @@ -183,7 +183,7 @@ function GenOverviewsForSingleLayer( callback: (layer: LayerConfig, element: BaseUIElement, inlineSource: string) => void ): void { const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter( - (layer) => Constants.priviliged_layers.indexOf(layer.id) < 0 + (layer) => layer.source !== null ) const builtinLayerIds: Set = new Set() allLayers.forEach((l) => builtinLayerIds.add(l.id)) @@ -195,7 +195,7 @@ function GenOverviewsForSingleLayer( } for (const layer of layout.layers) { - if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { + if (layer.source === null) { continue } if (builtinLayerIds.has(layer.id)) { diff --git a/scripts/generateStats.ts b/scripts/generateStats.ts index a86006412e..ab121bf7ad 100644 --- a/scripts/generateStats.ts +++ b/scripts/generateStats.ts @@ -18,7 +18,7 @@ async function main(includeTags = true) { if (layer.source["geoJson"] !== undefined && !layer.source["isOsmCache"]) { continue } - if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { + if (layer.source == null || typeof layer.source === "string") { continue } diff --git a/scripts/generateTaginfoProjectFiles.ts b/scripts/generateTaginfoProjectFiles.ts index a76803bc89..785f978f76 100644 --- a/scripts/generateTaginfoProjectFiles.ts +++ b/scripts/generateTaginfoProjectFiles.ts @@ -132,7 +132,7 @@ function generateLayerUsage(layer: LayerConfig, layout: LayoutConfig): any[] { function generateTagInfoEntry(layout: LayoutConfig): any { const usedTags = [] for (const layer of layout.layers) { - if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { + if (layer.source === null) { continue } if (layer.source.geojsonSource !== undefined && layer.source.isOsmCacheLayer !== true) { diff --git a/test.ts b/test.ts index c2d7915487..b1375d4ab6 100644 --- a/test.ts +++ b/test.ts @@ -3,11 +3,12 @@ import ThemeViewGUI from "./UI/ThemeViewGUI.svelte" import { FixedUiElement } from "./UI/Base/FixedUiElement" import { QueryParameters } from "./Logic/Web/QueryParameters" import { AllKnownLayoutsLazy } from "./Customizations/AllKnownLayouts" - +import LayoutConfig from "./Models/ThemeConfig/LayoutConfig" +import * as benches from "./assets/generated/themes/benches.json" async function main() { new FixedUiElement("Determining layout...").AttachTo("maindiv") - const qp = QueryParameters.GetQueryParameter("layout", "benches") - const layout = new AllKnownLayoutsLazy().get(qp.data) + const qp = QueryParameters.GetQueryParameter("layout", "") + const layout = new LayoutConfig(benches, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) console.log("Using layout", layout.id) new SvelteUIElement(ThemeViewGUI, { layout }).AttachTo("maindiv") } From b94a8f5745ba79083754ce2062aa035050a86087 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 26 Mar 2023 05:58:28 +0200 Subject: [PATCH 006/257] refactoring: more state splitting, basic layoutFeatureSource --- Logic/Actors/ChangeToElementsActor.ts | 6 +- Logic/Actors/OverpassFeatureSource.ts | 167 ++++------ Logic/Actors/SelectedElementTagsUpdater.ts | 55 ++-- Logic/BBox.ts | 3 + Logic/ElementStorage.ts | 120 ------- Logic/ExtraFunctions.ts | 15 - .../Actors/FeaturePropertiesStore.ts | 107 +++++++ .../Actors/MetaTagRecalculator.ts | 2 - .../RegisteringAllFromFeatureSourceActor.ts | 20 -- Logic/FeatureSource/FeaturePipeline.ts | 38 +-- Logic/FeatureSource/FeatureSource.ts | 2 +- Logic/FeatureSource/LayoutSource.ts | 129 ++++++++ .../PerLayerFeatureSourceSplitter.ts | 8 +- .../Sources/FeatureSourceMerger.ts | 84 ++--- .../Sources/FilteringFeatureSource.ts | 58 ++-- Logic/FeatureSource/Sources/GeoJsonSource.ts | 192 +++++------ .../Sources/SimpleFeatureSource.ts | 8 +- .../FeatureSource/TileFreshnessCalculator.ts | 64 ---- .../DynamicGeoJsonTileSource.ts | 44 +-- .../TiledFeatureSource/DynamicTileSource.ts | 116 +++---- .../TiledFeatureSource/OsmFeatureSource.ts | 148 ++++----- .../TiledFeatureSource/TileHierarchy.ts | 2 +- Logic/MetaTagging.ts | 34 +- Logic/Osm/Changes.ts | 19 +- Logic/Osm/ChangesetHandler.ts | 158 +++------- Logic/Osm/OsmConnection.ts | 158 ++++------ Logic/Osm/Overpass.ts | 9 +- Logic/Osm/RelationsTracker.ts | 76 ----- Logic/SimpleMetaTagger.ts | 297 ++++++++++-------- Logic/State/FeaturePipelineState.ts | 32 +- Models/MapProperties.ts | 2 - Models/ThemeConfig/DependencyCalculator.ts | 1 - Models/ThemeConfig/Json/LayerConfigJson.ts | 39 +-- Models/ThemeConfig/LayerConfig.ts | 4 +- Models/ThemeConfig/SourceConfig.ts | 2 - Models/TileRange.ts | 12 + UI/AllThemesGui.ts | 16 +- UI/BigComponents/FeaturedMessage.ts | 103 ------ .../CompareToAlreadyExistingNotes.ts | 2 - UI/Map/MapLibreAdaptor.ts | 9 +- UI/Map/ShowDataLayer.ts | 120 +++---- UI/Map/ShowDataLayerOptions.ts | 15 +- UI/Map/ShowDataMultiLayer.ts | 23 +- UI/ShowDataLayer/ShowOverlayLayer.ts | 21 -- .../ShowOverlayLayerImplementation.ts | 1 + UI/ShowDataLayer/TileHierarchyAggregator.ts | 257 --------------- UI/ThemeViewGUI.svelte | 38 ++- .../layers/grass_in_parks/grass_in_parks.json | 70 ----- assets/themes/speelplekken/speelplekken.json | 24 +- assets/welcome_message.json | 61 ---- css/index-tailwind-output.css | 11 +- scripts/generateCache.ts | 30 +- test.ts | 3 +- test/Logic/ExtraFunctions.spec.ts | 1 - 54 files changed, 1067 insertions(+), 1969 deletions(-) delete mode 100644 Logic/ElementStorage.ts create mode 100644 Logic/FeatureSource/Actors/FeaturePropertiesStore.ts delete mode 100644 Logic/FeatureSource/Actors/RegisteringAllFromFeatureSourceActor.ts create mode 100644 Logic/FeatureSource/LayoutSource.ts delete mode 100644 Logic/FeatureSource/TileFreshnessCalculator.ts delete mode 100644 Logic/Osm/RelationsTracker.ts delete mode 100644 UI/BigComponents/FeaturedMessage.ts delete mode 100644 UI/ShowDataLayer/ShowOverlayLayer.ts delete mode 100644 UI/ShowDataLayer/TileHierarchyAggregator.ts delete mode 100644 assets/layers/grass_in_parks/grass_in_parks.json delete mode 100644 assets/welcome_message.json diff --git a/Logic/Actors/ChangeToElementsActor.ts b/Logic/Actors/ChangeToElementsActor.ts index 1eac44bfd9..3354dbfa69 100644 --- a/Logic/Actors/ChangeToElementsActor.ts +++ b/Logic/Actors/ChangeToElementsActor.ts @@ -1,15 +1,15 @@ -import { ElementStorage } from "../ElementStorage" import { Changes } from "../Osm/Changes" +import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"; export default class ChangeToElementsActor { - constructor(changes: Changes, allElements: ElementStorage) { + constructor(changes: Changes, allElements: FeaturePropertiesStore) { changes.pendingChanges.addCallbackAndRun((changes) => { for (const change of changes) { const id = change.type + "/" + change.id if (!allElements.has(id)) { continue // Ignored as the geometryFixer will introduce this } - const src = allElements.getEventSourceById(id) + const src = allElements.getStore(id) let changed = false for (const kv of change.tags ?? []) { diff --git a/Logic/Actors/OverpassFeatureSource.ts b/Logic/Actors/OverpassFeatureSource.ts index bf8c44464b..acccf872e3 100644 --- a/Logic/Actors/OverpassFeatureSource.ts +++ b/Logic/Actors/OverpassFeatureSource.ts @@ -1,23 +1,19 @@ -import { Store, UIEventSource } from "../UIEventSource" +import { ImmutableStore, Store, UIEventSource } from "../UIEventSource" import { Or } from "../Tags/Or" import { Overpass } from "../Osm/Overpass" import FeatureSource from "../FeatureSource/FeatureSource" import { Utils } from "../../Utils" import { TagsFilter } from "../Tags/TagsFilter" -import SimpleMetaTagger from "../SimpleMetaTagger" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import RelationsTracker from "../Osm/RelationsTracker" import { BBox } from "../BBox" -import Loc from "../../Models/Loc" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import Constants from "../../Models/Constants" -import TileFreshnessCalculator from "../FeatureSource/TileFreshnessCalculator" -import { Tiles } from "../../Models/TileRange" import { Feature } from "geojson" +/** + * A wrapper around the 'Overpass'-object. + * It has more logic and will automatically fetch the data for the right bbox and the active layers + */ export default class OverpassFeatureSource implements FeatureSource { - public readonly name = "OverpassFeatureSource" - /** * The last loaded features, as geojson */ @@ -26,106 +22,67 @@ export default class OverpassFeatureSource implements FeatureSource { public readonly runningQuery: UIEventSource = new UIEventSource(false) public readonly timeout: UIEventSource = new UIEventSource(0) - public readonly relationsTracker: RelationsTracker - private readonly retries: UIEventSource = new UIEventSource(0) private readonly state: { - readonly locationControl: Store + readonly zoom: Store readonly layoutToUse: LayoutConfig readonly overpassUrl: Store readonly overpassTimeout: Store - readonly currentBounds: Store + readonly bounds: Store } private readonly _isActive: Store - /** - * Callback to handle all the data - */ - private readonly onBboxLoaded: ( - bbox: BBox, - date: Date, - layers: LayerConfig[], - zoomlevel: number - ) => void - - /** - * Keeps track of how fresh the data is - * @private - */ - private readonly freshnesses: Map + private readonly padToZoomLevel?: Store + private _lastQueryBBox: BBox constructor( state: { - readonly locationControl: Store readonly layoutToUse: LayoutConfig + readonly zoom: Store readonly overpassUrl: Store readonly overpassTimeout: Store readonly overpassMaxZoom: Store - readonly currentBounds: Store + readonly bounds: Store }, - options: { - padToTiles: Store + options?: { + padToTiles?: Store isActive?: Store - relationTracker: RelationsTracker - onBboxLoaded?: ( - bbox: BBox, - date: Date, - layers: LayerConfig[], - zoomlevel: number - ) => void - freshnesses?: Map } ) { this.state = state - this._isActive = options.isActive - this.onBboxLoaded = options.onBboxLoaded - this.relationsTracker = options.relationTracker - this.freshnesses = options.freshnesses + this._isActive = options?.isActive ?? new ImmutableStore(true) + this.padToZoomLevel = options?.padToTiles const self = this - state.currentBounds.addCallback((_) => { - self.update(options.padToTiles.data) + state.bounds.addCallbackD((_) => { + self.updateAsyncIfNeeded() }) } + /** + * Creates the 'Overpass'-object for the given layers + * @param interpreterUrl + * @param layersToDownload + * @constructor + * @private + */ private GetFilter(interpreterUrl: string, layersToDownload: LayerConfig[]): Overpass { - let filters: TagsFilter[] = [] - let extraScripts: string[] = [] - for (const layer of layersToDownload) { - if (layer.source.overpassScript !== undefined) { - extraScripts.push(layer.source.overpassScript) - } else { - filters.push(layer.source.osmTags) - } - } + let filters: TagsFilter[] = layersToDownload.map((layer) => layer.source.osmTags) filters = Utils.NoNull(filters) - extraScripts = Utils.NoNull(extraScripts) - if (filters.length + extraScripts.length === 0) { + if (filters.length === 0) { return undefined } - return new Overpass( - new Or(filters), - extraScripts, - interpreterUrl, - this.state.overpassTimeout, - this.relationsTracker - ) + return new Overpass(new Or(filters), [], interpreterUrl, this.state.overpassTimeout) } - private update(paddedZoomLevel: number) { - if (!this._isActive.data) { + /** + * + * @private + */ + private async updateAsyncIfNeeded(): Promise { + if (!this._isActive?.data) { + console.log("OverpassFeatureSource: not triggering as not active") return } - const self = this - this.updateAsync(paddedZoomLevel).then((bboxDate) => { - if (bboxDate === undefined || self.onBboxLoaded === undefined) { - return - } - const [bbox, date, layers] = bboxDate - self.onBboxLoaded(bbox, date, layers, paddedZoomLevel) - }) - } - - private async updateAsync(padToZoomLevel: number): Promise<[BBox, Date, LayerConfig[]]> { if (this.runningQuery.data) { console.log("Still running a query, not updating") return undefined @@ -135,15 +92,27 @@ export default class OverpassFeatureSource implements FeatureSource { console.log("Still in timeout - not updating") return undefined } + const requestedBounds = this.state.bounds.data + if ( + this._lastQueryBBox !== undefined && + requestedBounds.isContainedIn(this._lastQueryBBox) + ) { + return undefined + } + const [bounds, date, updatedLayers] = await this.updateAsync() + this._lastQueryBBox = bounds + } + /** + * Download the relevant data from overpass. Attempt to use a different server; only downloads the relevant layers + * @private + */ + private async updateAsync(): Promise<[BBox, Date, LayerConfig[]]> { let data: any = undefined let date: Date = undefined let lastUsed = 0 const layersToDownload = [] - const neededTiles = this.state.currentBounds.data - .expandToTileBounds(padToZoomLevel) - .containingTileRange(padToZoomLevel) for (const layer of this.state.layoutToUse.layers) { if (typeof layer === "string") { throw "A layer was not expanded!" @@ -151,7 +120,7 @@ export default class OverpassFeatureSource implements FeatureSource { if (layer.source === undefined) { continue } - if (this.state.locationControl.data.zoom < layer.minzoom) { + if (this.state.zoom.data < layer.minzoom) { continue } if (layer.doNotDownload) { @@ -161,31 +130,10 @@ export default class OverpassFeatureSource implements FeatureSource { // Not our responsibility to download this layer! continue } - const freshness = this.freshnesses?.get(layer.id) - if (freshness !== undefined) { - const oldestDataDate = - Math.min( - ...Tiles.MapRange(neededTiles, (x, y) => { - const date = freshness.freshnessFor(padToZoomLevel, x, y) - if (date === undefined) { - return 0 - } - return date.getTime() - }) - ) / 1000 - const now = new Date().getTime() - const minRequiredAge = now / 1000 - layer.maxAgeOfCache - if (oldestDataDate >= minRequiredAge) { - // still fresh enough - not updating - continue - } - } - layersToDownload.push(layer) } if (layersToDownload.length == 0) { - console.debug("Not updating - no layers needed") return } @@ -194,12 +142,13 @@ export default class OverpassFeatureSource implements FeatureSource { if (overpassUrls === undefined || overpassUrls.length === 0) { throw "Panic: overpassFeatureSource didn't receive any overpassUrls" } + // Note: the bounds are updated between attempts, in case that the user zoomed around let bounds: BBox do { try { - bounds = this.state.currentBounds.data + bounds = this.state.bounds.data ?.pad(this.state.layoutToUse.widenFactor) - ?.expandToTileBounds(padToZoomLevel) + ?.expandToTileBounds(this.padToZoomLevel?.data) if (bounds === undefined) { return undefined @@ -228,7 +177,6 @@ export default class OverpassFeatureSource implements FeatureSource { while (self.timeout.data > 0) { await Utils.waitFor(1000) - console.log(self.timeout.data) self.timeout.data-- self.timeout.ping() } @@ -240,14 +188,7 @@ export default class OverpassFeatureSource implements FeatureSource { if (data === undefined) { return undefined } - data.features.forEach((feature) => - SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature( - feature, - undefined, - this.state - ) - ) - self.features.setData(data.features.map((f) => ({ feature: f, freshness: date }))) + self.features.setData(data.features) return [bounds, date, layersToDownload] } catch (e) { console.error("Got the overpass response, but could not process it: ", e, e.stack) diff --git a/Logic/Actors/SelectedElementTagsUpdater.ts b/Logic/Actors/SelectedElementTagsUpdater.ts index 3df0a4804d..bb90fe28ba 100644 --- a/Logic/Actors/SelectedElementTagsUpdater.ts +++ b/Logic/Actors/SelectedElementTagsUpdater.ts @@ -2,12 +2,13 @@ * This actor will download the latest version of the selected element from OSM and update the tags if necessary. */ import { UIEventSource } from "../UIEventSource" -import { ElementStorage } from "../ElementStorage" import { Changes } from "../Osm/Changes" import { OsmObject } from "../Osm/OsmObject" import { OsmConnection } from "../Osm/OsmConnection" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import SimpleMetaTagger from "../SimpleMetaTagger" +import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" +import { Feature } from "geojson" export default class SelectedElementTagsUpdater { private static readonly metatags = new Set([ @@ -19,28 +20,34 @@ export default class SelectedElementTagsUpdater { "id", ]) + private readonly state: { + selectedElement: UIEventSource + allElements: FeaturePropertiesStore + changes: Changes + osmConnection: OsmConnection + layoutToUse: LayoutConfig + } + constructor(state: { - selectedElement: UIEventSource - allElements: ElementStorage + selectedElement: UIEventSource + allElements: FeaturePropertiesStore changes: Changes osmConnection: OsmConnection layoutToUse: LayoutConfig }) { + this.state = state state.osmConnection.isLoggedIn.addCallbackAndRun((isLoggedIn) => { - if (isLoggedIn) { - SelectedElementTagsUpdater.installCallback(state) - return true + if (!isLoggedIn) { + return } + this.installCallback() + // We only have to do this once... + return true }) } - public static installCallback(state: { - selectedElement: UIEventSource - allElements: ElementStorage - changes: Changes - osmConnection: OsmConnection - layoutToUse: LayoutConfig - }) { + private installCallback() { + const state = this.state state.selectedElement.addCallbackAndRunD(async (s) => { let id = s.properties?.id @@ -62,7 +69,7 @@ export default class SelectedElementTagsUpdater { const latestTags = await OsmObject.DownloadPropertiesOf(id) if (latestTags === "deleted") { console.warn("The current selected element has been deleted upstream!") - const currentTagsSource = state.allElements.getEventSourceById(id) + const currentTagsSource = state.allElements.getStore(id) if (currentTagsSource.data["_deleted"] === "yes") { return } @@ -70,25 +77,15 @@ export default class SelectedElementTagsUpdater { currentTagsSource.ping() return } - SelectedElementTagsUpdater.applyUpdate(state, latestTags, id) + this.applyUpdate(latestTags, id) console.log("Updated", id) } catch (e) { console.warn("Could not update", id, " due to", e) } }) } - - public static applyUpdate( - state: { - selectedElement: UIEventSource - allElements: ElementStorage - changes: Changes - osmConnection: OsmConnection - layoutToUse: LayoutConfig - }, - latestTags: any, - id: string - ) { + private applyUpdate(latestTags: any, id: string) { + const state = this.state try { const leftRightSensitive = state.layoutToUse.isLeftRightSensitive() @@ -115,7 +112,7 @@ export default class SelectedElementTagsUpdater { // With the changes applied, we merge them onto the upstream object let somethingChanged = false - const currentTagsSource = state.allElements.getEventSourceById(id) + const currentTagsSource = state.allElements.getStore(id) const currentTags = currentTagsSource.data for (const key in latestTags) { let osmValue = latestTags[key] @@ -135,7 +132,7 @@ export default class SelectedElementTagsUpdater { if (currentKey.startsWith("_")) { continue } - if (this.metatags.has(currentKey)) { + if (SelectedElementTagsUpdater.metatags.has(currentKey)) { continue } if (currentKey in latestTags) { diff --git a/Logic/BBox.ts b/Logic/BBox.ts index 3a875196e2..7f4214790b 100644 --- a/Logic/BBox.ts +++ b/Logic/BBox.ts @@ -214,6 +214,9 @@ export class BBox { * @param zoomlevel */ expandToTileBounds(zoomlevel: number): BBox { + if(zoomlevel === undefined){ + return this + } const ul = Tiles.embedded_tile(this.minLat, this.minLon, zoomlevel) const lr = Tiles.embedded_tile(this.maxLat, this.maxLon, zoomlevel) const boundsul = Tiles.tile_bounds_lon_lat(ul.z, ul.x, ul.y) diff --git a/Logic/ElementStorage.ts b/Logic/ElementStorage.ts deleted file mode 100644 index 49213b99f6..0000000000 --- a/Logic/ElementStorage.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Keeps track of a dictionary 'elementID' -> UIEventSource - */ -import { UIEventSource } from "./UIEventSource" -import { GeoJSONObject } from "@turf/turf" -import { Feature, Geometry, Point } from "geojson" -import { OsmTags } from "../Models/OsmFeature" - -export class ElementStorage { - public ContainingFeatures = new Map>() - private _elements = new Map>() - - constructor() {} - - addElementById(id: string, eventSource: UIEventSource) { - this._elements.set(id, eventSource) - } - - /** - * Creates a UIEventSource for the tags of the given feature. - * If an UIEventsource has been created previously, the same UIEventSource will be returned - * - * Note: it will cleverly merge the tags, if needed - */ - addOrGetElement(feature: Feature): UIEventSource { - const elementId = feature.properties.id - const newProperties = feature.properties - - const es = this.addOrGetById(elementId, newProperties) - - // At last, we overwrite the tag of the new feature to use the tags in the already existing event source - feature.properties = es.data - - if (!this.ContainingFeatures.has(elementId)) { - this.ContainingFeatures.set(elementId, feature) - } - - return es - } - - getEventSourceById(elementId): UIEventSource { - if (elementId === undefined) { - return undefined - } - return this._elements.get(elementId) - } - - has(id) { - return this._elements.has(id) - } - - addAlias(oldId: string, newId: string) { - if (newId === undefined) { - // We removed the node/way/relation with type 'type' and id 'oldId' on openstreetmap! - const element = this.getEventSourceById(oldId) - element.data._deleted = "yes" - element.ping() - return - } - - if (oldId == newId) { - return undefined - } - const element = this.getEventSourceById(oldId) - if (element === undefined) { - // Element to rewrite not found, probably a node or relation that is not rendered - return undefined - } - element.data.id = newId - this.addElementById(newId, element) - this.ContainingFeatures.set(newId, this.ContainingFeatures.get(oldId)) - element.ping() - } - - private addOrGetById(elementId: string, newProperties: any): UIEventSource { - if (!this._elements.has(elementId)) { - const eventSource = new UIEventSource(newProperties, "tags of " + elementId) - this._elements.set(elementId, eventSource) - return eventSource - } - - const es = this._elements.get(elementId) - if (es.data == newProperties) { - // Reference comparison gives the same object! we can just return the event source - return es - } - const keptKeys = es.data - // The element already exists - // We use the new feature to overwrite all the properties in the already existing eventsource - const debug_msg = [] - let somethingChanged = false - for (const k in newProperties) { - if (!newProperties.hasOwnProperty(k)) { - continue - } - const v = newProperties[k] - - if (keptKeys[k] !== v) { - if (v === undefined) { - // The new value is undefined; the tag might have been removed - // It might be a metatag as well - // In the latter case, we do keep the tag! - if (!k.startsWith("_")) { - delete keptKeys[k] - debug_msg.push("Erased " + k) - } - } else { - keptKeys[k] = v - debug_msg.push(k + " --> " + v) - } - - somethingChanged = true - } - } - if (somethingChanged) { - es.ping() - } - return es - } -} diff --git a/Logic/ExtraFunctions.ts b/Logic/ExtraFunctions.ts index 3da6f34b4f..da0c9ca0d2 100644 --- a/Logic/ExtraFunctions.ts +++ b/Logic/ExtraFunctions.ts @@ -14,7 +14,6 @@ export interface ExtraFuncParams { * Format: [ [ geojson, geojson, geojson, ... ], [geojson, ...], ...] */ getFeaturesWithin: (layerId: string, bbox: BBox) => Feature[][] - memberships: RelationsTracker getFeatureById: (id: string) => Feature } @@ -401,19 +400,6 @@ class ClosestNObjectFunc implements ExtraFunction { } } -class Memberships implements ExtraFunction { - _name = "memberships" - _doc = - "Gives a list of `{role: string, relation: Relation}`-objects, containing all the relations that this feature is part of. " + - "\n\n" + - "For example: `_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')`" - _args = [] - - _f(params, feat) { - return () => params.memberships.knownRelations.data.get(feat.properties.id) ?? [] - } -} - class GetParsed implements ExtraFunction { _name = "get" _doc = @@ -481,7 +467,6 @@ export class ExtraFunctions { new IntersectionFunc(), new ClosestObjectFunc(), new ClosestNObjectFunc(), - new Memberships(), new GetParsed(), ] diff --git a/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts b/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts new file mode 100644 index 0000000000..b4700e72bd --- /dev/null +++ b/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts @@ -0,0 +1,107 @@ +import FeatureSource, { IndexedFeatureSource } from "../FeatureSource" +import { UIEventSource } from "../../UIEventSource" + +/** + * Constructs a UIEventStore for the properties of every Feature, indexed by id + */ +export default class FeaturePropertiesStore { + private readonly _source: FeatureSource & IndexedFeatureSource + private readonly _elements = new Map>() + + constructor(source: FeatureSource & IndexedFeatureSource) { + this._source = source + const self = this + source.features.addCallbackAndRunD((features) => { + for (const feature of features) { + const id = feature.properties.id + if (id === undefined) { + console.trace("Error: feature without ID:", feature) + throw "Error: feature without ID" + } + + const source = self._elements.get(id) + if (source === undefined) { + self._elements.set(id, new UIEventSource(feature.properties)) + continue + } + + if (source.data === feature.properties) { + continue + } + + // Update the tags in the old store and link them + const changeMade = FeaturePropertiesStore.mergeTags(source.data, feature.properties) + feature.properties = source.data + if (changeMade) { + source.ping() + } + } + }) + } + + public getStore(id: string): UIEventSource> { + return this._elements.get(id) + } + + /** + * Overwrites the tags of the old properties object, returns true if a change was made. + * Metatags are overriden if they are in the new properties, but not removed + * @param oldProperties + * @param newProperties + * @private + */ + private static mergeTags( + oldProperties: Record, + newProperties: Record + ): boolean { + let changeMade = false + + for (const oldPropertiesKey in oldProperties) { + // Delete properties from the old record if it is not in the new store anymore + if (oldPropertiesKey.startsWith("_")) { + continue + } + if (newProperties[oldPropertiesKey] === undefined) { + changeMade = true + delete oldProperties[oldPropertiesKey] + } + } + + // Copy all properties from the new record into the old + for (const newPropertiesKey in newProperties) { + const v = newProperties[newPropertiesKey] + if (oldProperties[newPropertiesKey] !== v) { + oldProperties[newPropertiesKey] = v + changeMade = true + } + } + + return changeMade + } + + addAlias(oldId: string, newId: string): void { + if (newId === undefined) { + // We removed the node/way/relation with type 'type' and id 'oldId' on openstreetmap! + const element = this._elements.get(oldId) + element.data._deleted = "yes" + element.ping() + return + } + + if (oldId == newId) { + return + } + const element = this._elements.get(oldId) + if (element === undefined) { + // Element to rewrite not found, probably a node or relation that is not rendered + return + } + element.data.id = newId + this._elements.set(newId, element) + element.ping() + } + + has(id: string) { + return this._elements.has(id) + } +} diff --git a/Logic/FeatureSource/Actors/MetaTagRecalculator.ts b/Logic/FeatureSource/Actors/MetaTagRecalculator.ts index 165279b92c..aacf44b502 100644 --- a/Logic/FeatureSource/Actors/MetaTagRecalculator.ts +++ b/Logic/FeatureSource/Actors/MetaTagRecalculator.ts @@ -1,6 +1,5 @@ import { FeatureSourceForLayer, Tiled } from "../FeatureSource" import MetaTagging from "../../MetaTagging" -import { ElementStorage } from "../../ElementStorage" import { ExtraFuncParams } from "../../ExtraFunctions" import FeaturePipeline from "../FeaturePipeline" import { BBox } from "../../BBox" @@ -39,7 +38,6 @@ class MetatagUpdater { } return featurePipeline.GetFeaturesWithin(layerId, bbox) }, - memberships: featurePipeline.relationTracker, } this.isDirty.stabilized(100).addCallback((dirty) => { if (dirty) { diff --git a/Logic/FeatureSource/Actors/RegisteringAllFromFeatureSourceActor.ts b/Logic/FeatureSource/Actors/RegisteringAllFromFeatureSourceActor.ts deleted file mode 100644 index 756c11da3b..0000000000 --- a/Logic/FeatureSource/Actors/RegisteringAllFromFeatureSourceActor.ts +++ /dev/null @@ -1,20 +0,0 @@ -import FeatureSource from "../FeatureSource"; -import { Store } from "../../UIEventSource"; -import { ElementStorage } from "../../ElementStorage"; -import { Feature } from "geojson"; - -/** - * Makes sure that every feature is added to the ElementsStorage, so that the tags-eventsource can be retrieved - */ -export default class RegisteringAllFromFeatureSourceActor { - public readonly features: Store - - constructor(source: FeatureSource, allElements: ElementStorage) { - this.features = source.features - this.features.addCallbackAndRunD((features) => { - for (const feature of features) { - allElements.addOrGetElement( feature) - } - }) - } -} diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index 2492c0c107..fc7e668d79 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -13,16 +13,18 @@ import RegisteringAllFromFeatureSourceActor from "./Actors/RegisteringAllFromFea import SaveTileToLocalStorageActor from "./Actors/SaveTileToLocalStorageActor" import DynamicGeoJsonTileSource from "./TiledFeatureSource/DynamicGeoJsonTileSource" import { TileHierarchyMerger } from "./TiledFeatureSource/TileHierarchyMerger" -import RelationsTracker from "../Osm/RelationsTracker" import { NewGeometryFromChangesFeatureSource } from "./Sources/NewGeometryFromChangesFeatureSource" import ChangeGeometryApplicator from "./Sources/ChangeGeometryApplicator" +/** + * Keeps track of the age of the loaded data. + * Has one freshness-Calculator for every layer + * @private + */ import { BBox } from "../BBox" import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource" import { Tiles } from "../../Models/TileRange" -import TileFreshnessCalculator from "./TileFreshnessCalculator" import FullNodeDatabaseSource from "./TiledFeatureSource/FullNodeDatabaseSource" import MapState from "../State/MapState" -import { ElementStorage } from "../ElementStorage" import { OsmFeature } from "../../Models/OsmFeature" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { FilterState } from "../../Models/FilteredLayer" @@ -47,7 +49,6 @@ export default class FeaturePipeline { public readonly somethingLoaded: UIEventSource = new UIEventSource(false) public readonly newDataLoadedSignal: UIEventSource = new UIEventSource(undefined) - public readonly relationTracker: RelationsTracker /** * Keeps track of all raw OSM-nodes. * Only initialized if `ReplaceGeometryAction` is needed somewhere @@ -56,12 +57,6 @@ export default class FeaturePipeline { private readonly overpassUpdater: OverpassFeatureSource private state: MapState private readonly perLayerHierarchy: Map - /** - * Keeps track of the age of the loaded data. - * Has one freshness-Calculator for every layer - * @private - */ - private readonly freshnesses = new Map() private readonly oldestAllowedDate: Date private readonly osmSourceZoomLevel private readonly localStorageSavers = new Map() @@ -87,7 +82,6 @@ export default class FeaturePipeline { const useOsmApi = state.locationControl.map( (l) => l.zoom > (state.overpassMaxZoom.data ?? 12) ) - this.relationTracker = new RelationsTracker() state.changes.allChanges.addCallbackAndRun((allChanges) => { allChanges @@ -141,11 +135,8 @@ export default class FeaturePipeline { ) perLayerHierarchy.set(id, hierarchy) - this.freshnesses.set(id, new TileFreshnessCalculator()) - if (id === "type_node") { this.fullNodeDatabase = new FullNodeDatabaseSource(filteredLayer, (tile) => { - new RegisteringAllFromFeatureSourceActor(tile, state.allElements) perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile) tile.features.addCallbackAndRunD((_) => self.onNewDataLoaded(tile)) }) @@ -473,7 +464,6 @@ export default class FeaturePipeline { private initOverpassUpdater( state: { - allElements: ElementStorage layoutToUse: LayoutConfig currentBounds: Store locationControl: Store @@ -513,26 +503,10 @@ export default class FeaturePipeline { [state.locationControl] ) - const self = this - const updater = new OverpassFeatureSource(state, { + return new OverpassFeatureSource(state, { padToTiles: state.locationControl.map((l) => Math.min(15, l.zoom + 1)), - relationTracker: this.relationTracker, isActive: useOsmApi.map((b) => !b && overpassIsActive.data, [overpassIsActive]), - freshnesses: this.freshnesses, - onBboxLoaded: (bbox, date, downloadedLayers, paddedToZoomLevel) => { - Tiles.MapRange(bbox.containingTileRange(paddedToZoomLevel), (x, y) => { - const tileIndex = Tiles.tile_index(paddedToZoomLevel, x, y) - downloadedLayers.forEach((layer) => { - self.freshnesses.get(layer.id).addTileLoad(tileIndex, date) - self.localStorageSavers.get(layer.id)?.MarkVisited(tileIndex, date) - }) - }) - }, }) - - // Register everything in the state' 'AllElements' - new RegisteringAllFromFeatureSourceActor(updater, state.allElements) - return updater } /** diff --git a/Logic/FeatureSource/FeatureSource.ts b/Logic/FeatureSource/FeatureSource.ts index 243b8efd96..038132f48a 100644 --- a/Logic/FeatureSource/FeatureSource.ts +++ b/Logic/FeatureSource/FeatureSource.ts @@ -23,5 +23,5 @@ export interface FeatureSourceForLayer extends FeatureSource { * A feature source which is aware of the indexes it contains */ export interface IndexedFeatureSource extends FeatureSource { - readonly containedIds: Store> + readonly featuresById: Store> } diff --git a/Logic/FeatureSource/LayoutSource.ts b/Logic/FeatureSource/LayoutSource.ts new file mode 100644 index 0000000000..b4d62b49a2 --- /dev/null +++ b/Logic/FeatureSource/LayoutSource.ts @@ -0,0 +1,129 @@ +import FeatureSource from "./FeatureSource" +import { Store } from "../UIEventSource" +import FeatureSwitchState from "../State/FeatureSwitchState" +import OverpassFeatureSource from "../Actors/OverpassFeatureSource" +import { BBox } from "../BBox" +import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource" +import { Or } from "../Tags/Or" +import FeatureSourceMerger from "./Sources/FeatureSourceMerger" +import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import GeoJsonSource from "./Sources/GeoJsonSource" +import DynamicGeoJsonTileSource from "./TiledFeatureSource/DynamicGeoJsonTileSource" + +/** + * This source will fetch the needed data from various sources for the given layout. + * + * Note that special layers (with `source=null` will be ignored) + */ +export default class LayoutSource extends FeatureSourceMerger { + constructor( + filteredLayers: LayerConfig[], + featureSwitches: FeatureSwitchState, + newAndChangedElements: FeatureSource, + mapProperties: { bounds: Store; zoom: Store }, + backend: string, + isLayerActive: (id: string) => Store + ) { + const { bounds, zoom } = mapProperties + // remove all 'special' layers + filteredLayers = filteredLayers.filter((flayer) => flayer.source !== null) + + const geojsonlayers = filteredLayers.filter( + (flayer) => flayer.source.geojsonSource !== undefined + ) + const osmLayers = filteredLayers.filter( + (flayer) => flayer.source.geojsonSource === undefined + ) + const overpassSource = LayoutSource.setupOverpass(osmLayers, bounds, zoom, featureSwitches) + const osmApiSource = LayoutSource.setupOsmApiSource( + osmLayers, + bounds, + zoom, + backend, + featureSwitches + ) + const geojsonSources: FeatureSource[] = geojsonlayers.map((l) => + LayoutSource.setupGeojsonSource(l, mapProperties) + ) + + const expiryInSeconds = Math.min(...(filteredLayers?.map((l) => l.maxAgeOfCache) ?? [])) + super(overpassSource, osmApiSource, newAndChangedElements, ...geojsonSources) + } + + private static setupGeojsonSource( + layer: LayerConfig, + mapProperties: { zoom: Store; bounds: Store }, + isActive?: Store + ): FeatureSource { + const source = layer.source + if (source.geojsonZoomLevel === undefined) { + // This is a 'load everything at once' geojson layer + return new GeoJsonSource(layer, { isActive }) + } else { + return new DynamicGeoJsonTileSource(layer, mapProperties, { isActive }) + } + } + + private static setupOsmApiSource( + osmLayers: LayerConfig[], + bounds: Store, + zoom: Store, + backend: string, + featureSwitches: FeatureSwitchState + ): FeatureSource { + const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom)) + const isActive = zoom.mapD((z) => { + if (z < minzoom) { + // We are zoomed out over the zoomlevel of any layer + console.debug("Disabling overpass source: zoom < minzoom") + return false + } + + // Overpass should handle this if zoomed out a bit + return z > featureSwitches.overpassMaxZoom.data + }) + const allowedFeatures = new Or(osmLayers.map((l) => l.source.osmTags)).optimize() + if (typeof allowedFeatures === "boolean") { + throw "Invalid filter to init OsmFeatureSource: it optimizes away to " + allowedFeatures + } + return new OsmFeatureSource({ + allowedFeatures, + bounds, + backend, + isActive, + }) + } + + private static setupOverpass( + osmLayers: LayerConfig[], + bounds: Store, + zoom: Store, + featureSwitches: FeatureSwitchState + ): FeatureSource { + const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom)) + const isActive = zoom.mapD((z) => { + if (z < minzoom) { + // We are zoomed out over the zoomlevel of any layer + console.debug("Disabling overpass source: zoom < minzoom") + return false + } + + return z <= featureSwitches.overpassMaxZoom.data + }) + + return new OverpassFeatureSource( + { + zoom, + bounds, + layoutToUse: featureSwitches.layoutToUse, + overpassUrl: featureSwitches.overpassUrl, + overpassTimeout: featureSwitches.overpassTimeout, + overpassMaxZoom: featureSwitches.overpassMaxZoom, + }, + { + padToTiles: zoom.map((zoom) => Math.min(15, zoom + 1)), + isActive, + } + ) + } +} diff --git a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts index ef92543a7f..a2d17b5380 100644 --- a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts +++ b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts @@ -1,4 +1,4 @@ -import FeatureSource, { FeatureSourceForLayer, Tiled } from "./FeatureSource" +import FeatureSource from "./FeatureSource" import { Store } from "../UIEventSource" import FilteredLayer from "../../Models/FilteredLayer" import SimpleFeatureSource from "./Sources/SimpleFeatureSource" @@ -12,7 +12,7 @@ import { Feature } from "geojson" export default class PerLayerFeatureSourceSplitter { constructor( layers: Store, - handleLayerData: (source: FeatureSourceForLayer & Tiled) => void, + handleLayerData: (source: FeatureSource, layer: FilteredLayer) => void, upstream: FeatureSource, options?: { tileIndex?: number @@ -71,10 +71,10 @@ export default class PerLayerFeatureSourceSplitter { let featureSource = knownLayers.get(id) if (featureSource === undefined) { // Not yet initialized - now is a good time - featureSource = new SimpleFeatureSource(layer, options?.tileIndex) + featureSource = new SimpleFeatureSource(layer) featureSource.features.setData(features) knownLayers.set(id, featureSource) - handleLayerData(featureSource) + handleLayerData(featureSource, layer) } else { featureSource.features.setData(features) } diff --git a/Logic/FeatureSource/Sources/FeatureSourceMerger.ts b/Logic/FeatureSource/Sources/FeatureSourceMerger.ts index 8034ab6975..7221b2a03a 100644 --- a/Logic/FeatureSource/Sources/FeatureSourceMerger.ts +++ b/Logic/FeatureSource/Sources/FeatureSourceMerger.ts @@ -1,58 +1,40 @@ -import { UIEventSource } from "../../UIEventSource" -import FeatureSource, { FeatureSourceForLayer, IndexedFeatureSource, Tiled } from "../FeatureSource" -import FilteredLayer from "../../../Models/FilteredLayer" -import { BBox } from "../../BBox" +import { Store, UIEventSource } from "../../UIEventSource" +import FeatureSource, { IndexedFeatureSource } from "../FeatureSource" import { Feature } from "geojson" -export default class FeatureSourceMerger - implements FeatureSourceForLayer, Tiled, IndexedFeatureSource -{ +/** + * + */ +export default class FeatureSourceMerger implements IndexedFeatureSource { public features: UIEventSource = new UIEventSource([]) - public readonly layer: FilteredLayer - public readonly tileIndex: number - public readonly bbox: BBox - public readonly containedIds: UIEventSource> = new UIEventSource>( - new Set() - ) - private readonly _sources: UIEventSource + public readonly featuresById: Store> + private readonly _featuresById: UIEventSource> + private readonly _sources: FeatureSource[] = [] /** - * Merges features from different featureSources for a single layer - * Uses the freshest feature available in the case multiple sources offer data with the same identifier + * Merges features from different featureSources. + * In case that multiple features have the same id, the latest `_version_number` will be used. Otherwise, we will take the last one */ - constructor( - layer: FilteredLayer, - tileIndex: number, - bbox: BBox, - sources: UIEventSource - ) { - this.tileIndex = tileIndex - this.bbox = bbox - this._sources = sources - this.layer = layer + constructor(...sources: FeatureSource[]) { + this._featuresById = new UIEventSource>(undefined) + this.featuresById = this._featuresById const self = this + for (let source of sources) { + source.features.addCallback(() => { + self.addData(sources.map((s) => s.features.data)) + }) + } + this.addData(sources.map((s) => s.features.data)) + this._sources = sources + } - const handledSources = new Set() - - sources.addCallbackAndRunD((sources) => { - let newSourceRegistered = false - for (let i = 0; i < sources.length; i++) { - let source = sources[i] - if (handledSources.has(source)) { - continue - } - handledSources.add(source) - newSourceRegistered = true - source.features.addCallback(() => { - self.Update() - }) - if (newSourceRegistered) { - self.Update() - } - } + protected addSource(source: FeatureSource) { + this._sources.push(source) + source.features.addCallbackAndRun(() => { + this.addData(this._sources.map((s) => s.features.data)) }) } - private Update() { + protected addData(featuress: Feature[][]) { let somethingChanged = false const all: Map = new Map() // We seed the dictionary with the previously loaded features @@ -61,11 +43,11 @@ export default class FeatureSourceMerger all.set(oldValue.properties.id, oldValue) } - for (const source of this._sources.data) { - if (source?.features?.data === undefined) { + for (const features of featuress) { + if (features === undefined) { continue } - for (const f of source.features.data) { + for (const f of features) { const id = f.properties.id if (!all.has(id)) { // This is a new feature @@ -77,7 +59,7 @@ export default class FeatureSourceMerger // This value has been seen already, either in a previous run or by a previous datasource // Let's figure out if something changed const oldV = all.get(id) - if (oldV === f) { + if (oldV == f) { continue } all.set(id, f) @@ -91,10 +73,10 @@ export default class FeatureSourceMerger } const newList = [] - all.forEach((value, _) => { + all.forEach((value, key) => { newList.push(value) }) - this.containedIds.setData(new Set(all.keys())) this.features.setData(newList) + this._featuresById.setData(all) } } diff --git a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts index 2a36553731..dcbbef71ef 100644 --- a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts @@ -1,45 +1,32 @@ import { Store, UIEventSource } from "../../UIEventSource" import FilteredLayer, { FilterState } from "../../../Models/FilteredLayer" -import { FeatureSourceForLayer, Tiled } from "../FeatureSource" -import { BBox } from "../../BBox" -import { ElementStorage } from "../../ElementStorage" +import FeatureSource from "../FeatureSource" import { TagsFilter } from "../../Tags/TagsFilter" import { Feature } from "geojson" +import { OsmTags } from "../../../Models/OsmFeature" -export default class FilteringFeatureSource implements FeatureSourceForLayer, Tiled { +export default class FilteringFeatureSource implements FeatureSource { public features: UIEventSource = new UIEventSource([]) - public readonly layer: FilteredLayer - public readonly tileIndex: number - public readonly bbox: BBox - private readonly upstream: FeatureSourceForLayer - private readonly state: { - locationControl: Store<{ zoom: number }> - selectedElement: Store - globalFilters?: Store<{ filter: FilterState }[]> - allElements: ElementStorage - } - private readonly _alreadyRegistered = new Set>() + private readonly upstream: FeatureSource + private readonly _fetchStore?: (id: String) => Store + private readonly _globalFilters?: Store<{ filter: FilterState }[]> + private readonly _alreadyRegistered = new Set>() private readonly _is_dirty = new UIEventSource(false) + private readonly _layer: FilteredLayer private previousFeatureSet: Set = undefined constructor( - state: { - locationControl: Store<{ zoom: number }> - selectedElement: Store - allElements: ElementStorage - globalFilters?: Store<{ filter: FilterState }[]> - }, - tileIndex, - upstream: FeatureSourceForLayer, - metataggingUpdated?: UIEventSource + layer: FilteredLayer, + upstream: FeatureSource, + fetchStore?: (id: String) => Store, + globalFilters?: Store<{ filter: FilterState }[]>, + metataggingUpdated?: Store ) { - this.tileIndex = tileIndex - this.bbox = tileIndex === undefined ? undefined : BBox.fromTileIndex(tileIndex) this.upstream = upstream - this.state = state + this._fetchStore = fetchStore + this._layer = layer + this._globalFilters = globalFilters - this.layer = upstream.layer - const layer = upstream.layer const self = this upstream.features.addCallback(() => { self.update() @@ -59,7 +46,7 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti self._is_dirty.setData(true) }) - state.globalFilters?.addCallback((_) => { + globalFilters?.addCallback((_) => { self.update() }) @@ -68,10 +55,10 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti private update() { const self = this - const layer = this.upstream.layer + const layer = this._layer const features: Feature[] = this.upstream.features.data ?? [] const includedFeatureIds = new Set() - const globalFilters = self.state.globalFilters?.data?.map((f) => f.filter) + const globalFilters = self._globalFilters?.data?.map((f) => f.filter) const newFeatures = (features ?? []).filter((f) => { self.registerCallback(f) @@ -126,7 +113,10 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti } private registerCallback(feature: any) { - const src = this.state?.allElements?.addOrGetElement(feature) + if (this._fetchStore === undefined) { + return + } + const src = this._fetchStore(feature) if (src == undefined) { return } @@ -136,7 +126,7 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti this._alreadyRegistered.add(src) const self = this - // Add a callback as a changed tag migh change the filter + // Add a callback as a changed tag might change the filter src.addCallbackAndRunD((_) => { self._is_dirty.setData(true) }) diff --git a/Logic/FeatureSource/Sources/GeoJsonSource.ts b/Logic/FeatureSource/Sources/GeoJsonSource.ts index 99525508bf..30e4fa6048 100644 --- a/Logic/FeatureSource/Sources/GeoJsonSource.ts +++ b/Logic/FeatureSource/Sources/GeoJsonSource.ts @@ -1,59 +1,53 @@ /** * Fetches a geojson file somewhere and passes it along */ -import { UIEventSource } from "../../UIEventSource" -import FilteredLayer from "../../../Models/FilteredLayer" +import { Store, UIEventSource } from "../../UIEventSource" import { Utils } from "../../../Utils" -import { FeatureSourceForLayer, Tiled } from "../FeatureSource" -import { Tiles } from "../../../Models/TileRange" +import FeatureSource from "../FeatureSource" import { BBox } from "../../BBox" import { GeoOperations } from "../../GeoOperations" import { Feature } from "geojson" +import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" +import { Tiles } from "../../../Models/TileRange" -export default class GeoJsonSource implements FeatureSourceForLayer, Tiled { - public readonly features: UIEventSource - public readonly state = new UIEventSource(undefined) - public readonly name - public readonly isOsmCache: boolean - public readonly layer: FilteredLayer - public readonly tileIndex - public readonly bbox +export default class GeoJsonSource implements FeatureSource { + public readonly features: Store private readonly seenids: Set private readonly idKey?: string public constructor( - flayer: FilteredLayer, - zxy?: [number, number, number] | BBox, + layer: LayerConfig, options?: { + zxy?: number | [number, number, number] | BBox featureIdBlacklist?: Set + isActive?: Store } ) { - if (flayer.layerDef.source.geojsonZoomLevel !== undefined && zxy === undefined) { + if (layer.source.geojsonZoomLevel !== undefined && options?.zxy === undefined) { throw "Dynamic layers are not supported. Use 'DynamicGeoJsonTileSource instead" } - this.layer = flayer - this.idKey = flayer.layerDef.source.idKey + this.idKey = layer.source.idKey this.seenids = options?.featureIdBlacklist ?? new Set() - let url = flayer.layerDef.source.geojsonSource.replace("{layer}", flayer.layerDef.id) + let url = layer.source.geojsonSource.replace("{layer}", layer.id) + let zxy = options?.zxy if (zxy !== undefined) { let tile_bbox: BBox + if (typeof zxy === "number") { + zxy = Tiles.tile_from_index(zxy) + } if (zxy instanceof BBox) { tile_bbox = zxy } else { const [z, x, y] = zxy tile_bbox = BBox.fromTile(z, x, y) - - this.tileIndex = Tiles.tile_index(z, x, y) - this.bbox = BBox.fromTile(z, x, y) url = url .replace("{z}", "" + z) .replace("{x}", "" + x) .replace("{y}", "" + y) } - let bounds: { minLat: number; maxLat: number; minLon: number; maxLon: number } = - tile_bbox - if (this.layer.layerDef.source.mercatorCrs) { + let bounds: Record<"minLat" | "maxLat" | "minLon" | "maxLon", number> = tile_bbox + if (layer.source.mercatorCrs) { bounds = tile_bbox.toMercator() } @@ -62,103 +56,83 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled { .replace("{y_max}", "" + bounds.maxLat) .replace("{x_min}", "" + bounds.minLon) .replace("{x_max}", "" + bounds.maxLon) - } else { - this.tileIndex = Tiles.tile_index(0, 0, 0) - this.bbox = BBox.global } - this.name = "GeoJsonSource of " + url - - this.isOsmCache = flayer.layerDef.source.isOsmCacheLayer - this.features = new UIEventSource([]) - this.LoadJSONFrom(url) + const eventsource = new UIEventSource(undefined) + if (options?.isActive !== undefined) { + options.isActive.addCallbackAndRunD(async (active) => { + if (!active) { + return + } + this.LoadJSONFrom(url, eventsource, layer) + .then((_) => console.log("Loaded geojson " + url)) + .catch((err) => console.error("Could not load ", url, "due to", err)) + return true + }) + } else { + this.LoadJSONFrom(url, eventsource, layer) + .then((_) => console.log("Loaded geojson " + url)) + .catch((err) => console.error("Could not load ", url, "due to", err)) + } + this.features = eventsource } - private LoadJSONFrom(url: string) { - const eventSource = this.features + private async LoadJSONFrom( + url: string, + eventSource: UIEventSource, + layer: LayerConfig + ): Promise { const self = this - Utils.downloadJsonCached(url, 60 * 60) - .then((json) => { - self.state.setData("loaded") - // TODO: move somewhere else, just for testing - // Check for maproulette data - if (url.startsWith("https://maproulette.org/api/v2/tasks/box/")) { - console.log("MapRoulette data detected") - const data = json - let maprouletteFeatures: any[] = [] - data.forEach((element) => { - maprouletteFeatures.push({ - type: "Feature", - geometry: { - type: "Point", - coordinates: [element.point.lng, element.point.lat], - }, - properties: { - // Map all properties to the feature - ...element, - }, - }) - }) - json.features = maprouletteFeatures + let json = await Utils.downloadJsonCached(url, 60 * 60) + + if (json.features === undefined || json.features === null) { + json.features = [] + } + + if (layer.source.mercatorCrs) { + json = GeoOperations.GeoJsonToWGS84(json) + } + + const time = new Date() + const newFeatures: Feature[] = [] + let i = 0 + let skipped = 0 + for (const feature of json.features) { + const props = feature.properties + for (const key in props) { + if (props[key] === null) { + delete props[key] } - if (json.features === undefined || json.features === null) { - return + if (typeof props[key] !== "string") { + // Make sure all the values are string, it crashes stuff otherwise + props[key] = JSON.stringify(props[key]) } + } - if (self.layer.layerDef.source.mercatorCrs) { - json = GeoOperations.GeoJsonToWGS84(json) - } + if (self.idKey !== undefined) { + props.id = props[self.idKey] + } - const time = new Date() - const newFeatures: Feature[] = [] - let i = 0 - let skipped = 0 - for (const feature of json.features) { - const props = feature.properties - for (const key in props) { - if (props[key] === null) { - delete props[key] - } + if (props.id === undefined) { + props.id = url + "/" + i + feature.id = url + "/" + i + i++ + } + if (self.seenids.has(props.id)) { + skipped++ + continue + } + self.seenids.add(props.id) - if (typeof props[key] !== "string") { - // Make sure all the values are string, it crashes stuff otherwise - props[key] = JSON.stringify(props[key]) - } - } + let freshness: Date = time + if (feature.properties["_last_edit:timestamp"] !== undefined) { + freshness = new Date(props["_last_edit:timestamp"]) + } - if (self.idKey !== undefined) { - props.id = props[self.idKey] - } + newFeatures.push(feature) + } - if (props.id === undefined) { - props.id = url + "/" + i - feature.id = url + "/" + i - i++ - } - if (self.seenids.has(props.id)) { - skipped++ - continue - } - self.seenids.add(props.id) - - let freshness: Date = time - if (feature.properties["_last_edit:timestamp"] !== undefined) { - freshness = new Date(props["_last_edit:timestamp"]) - } - - newFeatures.push(feature) - } - - if (newFeatures.length == 0) { - return - } - - eventSource.setData(eventSource.data.concat(newFeatures)) - }) - .catch((msg) => { - console.debug("Could not load geojson layer", url, "due to", msg) - self.state.setData({ error: msg }) - }) + eventSource.setData(newFeatures) } } diff --git a/Logic/FeatureSource/Sources/SimpleFeatureSource.ts b/Logic/FeatureSource/Sources/SimpleFeatureSource.ts index ca08543374..543a26ad5c 100644 --- a/Logic/FeatureSource/Sources/SimpleFeatureSource.ts +++ b/Logic/FeatureSource/Sources/SimpleFeatureSource.ts @@ -4,16 +4,12 @@ import { FeatureSourceForLayer, Tiled } from "../FeatureSource" import { BBox } from "../../BBox" import { Feature } from "geojson" -export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled { +export default class SimpleFeatureSource implements FeatureSourceForLayer { public readonly features: UIEventSource public readonly layer: FilteredLayer - public readonly bbox: BBox = BBox.global - public readonly tileIndex: number - constructor(layer: FilteredLayer, tileIndex: number, featureSource?: UIEventSource) { + constructor(layer: FilteredLayer, featureSource?: UIEventSource) { this.layer = layer - this.tileIndex = tileIndex ?? 0 - this.bbox = BBox.fromTileIndex(this.tileIndex) this.features = featureSource ?? new UIEventSource([]) } } diff --git a/Logic/FeatureSource/TileFreshnessCalculator.ts b/Logic/FeatureSource/TileFreshnessCalculator.ts deleted file mode 100644 index 3d1adde97d..0000000000 --- a/Logic/FeatureSource/TileFreshnessCalculator.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Tiles } from "../../Models/TileRange" - -export default class TileFreshnessCalculator { - /** - * All the freshnesses per tile index - * @private - */ - private readonly freshnesses = new Map() - - /** - * Marks that some data got loaded for this layer - * @param tileId - * @param freshness - */ - public addTileLoad(tileId: number, freshness: Date) { - const existingFreshness = this.freshnessFor(...Tiles.tile_from_index(tileId)) - if (existingFreshness >= freshness) { - return - } - this.freshnesses.set(tileId, freshness) - - // Do we have freshness for the neighbouring tiles? If so, we can mark the tile above as loaded too! - let [z, x, y] = Tiles.tile_from_index(tileId) - if (z === 0) { - return - } - x = x - (x % 2) // Make the tiles always even - y = y - (y % 2) - - const ul = this.freshnessFor(z, x, y)?.getTime() - if (ul === undefined) { - return - } - const ur = this.freshnessFor(z, x + 1, y)?.getTime() - if (ur === undefined) { - return - } - const ll = this.freshnessFor(z, x, y + 1)?.getTime() - if (ll === undefined) { - return - } - const lr = this.freshnessFor(z, x + 1, y + 1)?.getTime() - if (lr === undefined) { - return - } - - const leastFresh = Math.min(ul, ur, ll, lr) - const date = new Date() - date.setTime(leastFresh) - this.addTileLoad(Tiles.tile_index(z - 1, Math.floor(x / 2), Math.floor(y / 2)), date) - } - - public freshnessFor(z: number, x: number, y: number): Date { - if (z < 0) { - return undefined - } - const tileId = Tiles.tile_index(z, x, y) - if (this.freshnesses.has(tileId)) { - return this.freshnesses.get(tileId) - } - // recurse up - return this.freshnessFor(z - 1, Math.floor(x / 2), Math.floor(y / 2)) - } -} diff --git a/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts b/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts index dd2b69ffe2..30ed0cb377 100644 --- a/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts @@ -1,23 +1,24 @@ -import FilteredLayer from "../../../Models/FilteredLayer" -import { FeatureSourceForLayer, Tiled } from "../FeatureSource" -import { UIEventSource } from "../../UIEventSource" +import { Store } from "../../UIEventSource" import DynamicTileSource from "./DynamicTileSource" import { Utils } from "../../../Utils" import GeoJsonSource from "../Sources/GeoJsonSource" import { BBox } from "../../BBox" +import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" export default class DynamicGeoJsonTileSource extends DynamicTileSource { private static whitelistCache = new Map() constructor( - layer: FilteredLayer, - registerLayer: (layer: FeatureSourceForLayer & Tiled) => void, - state: { - locationControl?: UIEventSource<{ zoom?: number }> - currentBounds: UIEventSource + layer: LayerConfig, + mapProperties: { + zoom: Store + bounds: Store + }, + options?: { + isActive?: Store } ) { - const source = layer.layerDef.source + const source = layer.source if (source.geojsonZoomLevel === undefined) { throw "Invalid layer: geojsonZoomLevel expected" } @@ -30,7 +31,7 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource { const whitelistUrl = source.geojsonSource .replace("{z}", "" + source.geojsonZoomLevel) .replace("{x}_{y}.geojson", "overview.json") - .replace("{layer}", layer.layerDef.id) + .replace("{layer}", layer.id) if (DynamicGeoJsonTileSource.whitelistCache.has(whitelistUrl)) { whitelist = DynamicGeoJsonTileSource.whitelistCache.get(whitelistUrl) @@ -56,14 +57,13 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource { DynamicGeoJsonTileSource.whitelistCache.set(whitelistUrl, whitelist) }) .catch((err) => { - console.warn("No whitelist found for ", layer.layerDef.id, err) + console.warn("No whitelist found for ", layer.id, err) }) } } const blackList = new Set() super( - layer, source.geojsonZoomLevel, (zxy) => { if (whitelist !== undefined) { @@ -78,25 +78,13 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource { } } - const src = new GeoJsonSource(layer, zxy, { + return new GeoJsonSource(layer, { + zxy, featureIdBlacklist: blackList, }) - - registerLayer(src) - return src }, - state + mapProperties, + { isActive: options.isActive } ) } - - public static RegisterWhitelist(url: string, json: any) { - const data = new Map>() - for (const x in json) { - if (x === "zoom") { - continue - } - data.set(Number(x), new Set(json[x])) - } - DynamicGeoJsonTileSource.whitelistCache.set(url, data) - } } diff --git a/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts b/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts index 78f7598184..3b3953396e 100644 --- a/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts @@ -1,87 +1,65 @@ -import FilteredLayer from "../../../Models/FilteredLayer" -import { FeatureSourceForLayer, Tiled } from "../FeatureSource" -import { UIEventSource } from "../../UIEventSource" -import TileHierarchy from "./TileHierarchy" +import { Store, Stores } from "../../UIEventSource" import { Tiles } from "../../../Models/TileRange" import { BBox } from "../../BBox" +import FeatureSource from "../FeatureSource" +import FeatureSourceMerger from "../Sources/FeatureSourceMerger" /*** * A tiled source which dynamically loads the required tiles at a fixed zoom level */ -export default class DynamicTileSource implements TileHierarchy { - public readonly loadedTiles: Map - private readonly _loadedTiles = new Set() - +export default class DynamicTileSource extends FeatureSourceMerger { constructor( - layer: FilteredLayer, zoomlevel: number, - constructTile: (zxy: [number, number, number]) => FeatureSourceForLayer & Tiled, - state: { - currentBounds: UIEventSource - locationControl?: UIEventSource<{ zoom?: number }> + constructSource: (tileIndex) => FeatureSource, + mapProperties: { + bounds: Store + zoom: Store + }, + options?: { + isActive?: Store } ) { - const self = this - - this.loadedTiles = new Map() - const neededTiles = state.currentBounds - .map( - (bounds) => { - if (bounds === undefined) { - // We'll retry later - return undefined - } - - if (!layer.isDisplayed.data && !layer.layerDef.forceLoad) { - // No need to download! - the layer is disabled - return undefined - } - - if ( - state.locationControl?.data?.zoom !== undefined && - state.locationControl.data.zoom < layer.layerDef.minzoom - ) { - // No need to download! - the layer is disabled - return undefined - } - - const tileRange = Tiles.TileRangeBetween( - zoomlevel, - bounds.getNorth(), - bounds.getEast(), - bounds.getSouth(), - bounds.getWest() - ) - if (tileRange.total > 10000) { - console.error( - "Got a really big tilerange, bounds and location might be out of sync" + super() + const loadedTiles = new Set() + const neededTiles: Store = Stores.ListStabilized( + mapProperties.bounds + .mapD( + (bounds) => { + if (options?.isActive?.data === false) { + // No need to download! - the layer is disabled + return undefined + } + const tileRange = Tiles.TileRangeBetween( + zoomlevel, + bounds.getNorth(), + bounds.getEast(), + bounds.getSouth(), + bounds.getWest() ) - return undefined - } + if (tileRange.total > 10000) { + console.error( + "Got a really big tilerange, bounds and location might be out of sync" + ) + return undefined + } - const needed = Tiles.MapRange(tileRange, (x, y) => - Tiles.tile_index(zoomlevel, x, y) - ).filter((i) => !self._loadedTiles.has(i)) - if (needed.length === 0) { - return undefined - } - return needed - }, - [layer.isDisplayed, state.locationControl] - ) - .stabilized(250) + const needed = Tiles.MapRange(tileRange, (x, y) => + Tiles.tile_index(zoomlevel, x, y) + ).filter((i) => !loadedTiles.has(i)) + if (needed.length === 0) { + return undefined + } + return needed + }, + [options?.isActive, mapProperties.zoom] + ) + .stabilized(250) + ) neededTiles.addCallbackAndRunD((neededIndexes) => { - console.log("Tiled geojson source ", layer.layerDef.id, " needs", neededIndexes) - if (neededIndexes === undefined) { - return - } for (const neededIndex of neededIndexes) { - self._loadedTiles.add(neededIndex) - const src = constructTile(Tiles.tile_from_index(neededIndex)) - if (src !== undefined) { - self.loadedTiles.set(neededIndex, src) - } + loadedTiles.add(neededIndex) + super.addSource(constructSource(neededIndex)) } }) } diff --git a/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts b/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts index d2ada64268..2df3358111 100644 --- a/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts @@ -1,93 +1,68 @@ import { Utils } from "../../../Utils" import OsmToGeoJson from "osmtogeojson" -import StaticFeatureSource from "../Sources/StaticFeatureSource" -import PerLayerFeatureSourceSplitter from "../PerLayerFeatureSourceSplitter" -import { Store, UIEventSource } from "../../UIEventSource" -import FilteredLayer from "../../../Models/FilteredLayer" -import { FeatureSourceForLayer, Tiled } from "../FeatureSource" +import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" import { Tiles } from "../../../Models/TileRange" import { BBox } from "../../BBox" -import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig" -import { Or } from "../../Tags/Or" import { TagsFilter } from "../../Tags/TagsFilter" import { OsmObject } from "../../Osm/OsmObject" -import { FeatureCollection } from "@turf/turf" +import { Feature } from "geojson" +import FeatureSourceMerger from "../Sources/FeatureSourceMerger" /** * If a tile is needed (requested via the UIEventSource in the constructor), will download the appropriate tile and pass it via 'handleTile' */ -export default class OsmFeatureSource { - public readonly isRunning: UIEventSource = new UIEventSource(false) - public readonly downloadedTiles = new Set() - public rawDataHandlers: ((osmJson: any, tileId: number) => void)[] = [] +export default class OsmFeatureSource extends FeatureSourceMerger { + private readonly _bounds: Store + private readonly isActive: Store private readonly _backend: string - private readonly filteredLayers: Store - private readonly handleTile: (fs: FeatureSourceForLayer & Tiled) => void - private isActive: Store - private options: { - handleTile: (tile: FeatureSourceForLayer & Tiled) => void - isActive: Store - neededTiles: Store - markTileVisited?: (tileId: number) => void - } private readonly allowedTags: TagsFilter + public readonly isRunning: UIEventSource = new UIEventSource(false) + public rawDataHandlers: ((osmJson: any, tileIndex: number) => void)[] = [] + + private readonly _downloadedTiles: Set = new Set() + private readonly _downloadedData: Feature[][] = [] /** - * - * @param options: allowedFeatures is normally calculated from the layoutToUse + * Downloads data directly from the OSM-api within the given bounds. + * All features which match the TagsFilter 'allowedFeatures' are kept and converted into geojson */ constructor(options: { - handleTile: (tile: FeatureSourceForLayer & Tiled) => void - isActive: Store - neededTiles: Store - state: { - readonly filteredLayers: UIEventSource - readonly osmConnection: { - Backend(): string - } - readonly layoutToUse?: LayoutConfig - } - readonly allowedFeatures?: TagsFilter - markTileVisited?: (tileId: number) => void + bounds: Store + readonly allowedFeatures: TagsFilter + backend?: "https://openstreetmap.org/" | string + /** + * If given: this featureSwitch will not update if the store contains 'false' + */ + isActive?: Store }) { - this.options = options - this._backend = options.state.osmConnection.Backend() - this.filteredLayers = options.state.filteredLayers.map((layers) => - layers.filter((layer) => layer.layerDef.source.geojsonSource === undefined) - ) - this.handleTile = options.handleTile - this.isActive = options.isActive - const self = this - options.neededTiles.addCallbackAndRunD((neededTiles) => { - self.Update(neededTiles) - }) - - const neededLayers = (options.state.layoutToUse?.layers ?? []) - .filter((layer) => !layer.doNotDownload) - .filter( - (layer) => layer.source.geojsonSource === undefined || layer.source.isOsmCacheLayer - ) - this.allowedTags = - options.allowedFeatures ?? new Or(neededLayers.map((l) => l.source.osmTags)) + super() + this._bounds = options.bounds + this.allowedTags = options.allowedFeatures + this.isActive = options.isActive ?? new ImmutableStore(true) + this._backend = options.backend ?? "https://www.openstreetmap.org" + this._bounds.addCallbackAndRunD((bbox) => this.loadData(bbox)) + console.log("Allowed tags are:", this.allowedTags) } - private async Update(neededTiles: number[]) { - if (this.options.isActive?.data === false) { + private async loadData(bbox: BBox) { + if (this.isActive?.data === false) { + console.log("OsmFeatureSource: not triggering: inactive") return } - neededTiles = neededTiles.filter((tile) => !this.downloadedTiles.has(tile)) + const z = 15 + const neededTiles = Tiles.tileRangeFrom(bbox, z) - if (neededTiles.length == 0) { + if (neededTiles.total == 0) { return } this.isRunning.setData(true) try { - for (const neededTile of neededTiles) { - this.downloadedTiles.add(neededTile) - await this.LoadTile(...Tiles.tile_from_index(neededTile)) - } + const tileNumbers = Tiles.MapRange(neededTiles, (x, y) => { + return Tiles.tile_index(z, x, y) + }) + await Promise.all(tileNumbers.map((i) => this.LoadTile(...Tiles.tile_from_index(i)))) } catch (e) { console.error(e) } finally { @@ -95,6 +70,11 @@ export default class OsmFeatureSource { } } + private registerFeatures(features: Feature[]): void { + this._downloadedData.push(features) + super.addData(this._downloadedData) + } + /** * The requested tile might only contain part of the relation. * @@ -135,6 +115,11 @@ export default class OsmFeatureSource { if (z < 14) { throw `Zoom ${z} is too much for OSM to handle! Use a higher zoom level!` } + const index = Tiles.tile_index(z, x, y) + if (this._downloadedTiles.has(index)) { + return + } + this._downloadedTiles.add(index) const bbox = BBox.fromTile(z, x, y) const url = `${this._backend}/api/0.6/map?bbox=${bbox.minLon},${bbox.minLat},${bbox.maxLon},${bbox.maxLat}` @@ -146,43 +131,28 @@ export default class OsmFeatureSource { this.rawDataHandlers.forEach((handler) => handler(osmJson, Tiles.tile_index(z, x, y)) ) - const geojson = >OsmToGeoJson( + let features = []>OsmToGeoJson( osmJson, // @ts-ignore { flatProperties: true, } - ) + ).features // The geojson contains _all_ features at the given location // We only keep what is needed - geojson.features = geojson.features.filter((feature) => + features = features.filter((feature) => this.allowedTags.matchesProperties(feature.properties) ) - for (let i = 0; i < geojson.features.length; i++) { - geojson.features[i] = await this.patchIncompleteRelations( - geojson.features[i], - osmJson - ) + for (let i = 0; i < features.length; i++) { + features[i] = await this.patchIncompleteRelations(features[i], osmJson) } - geojson.features.forEach((f) => { + features.forEach((f) => { f.properties["_backend"] = this._backend }) - - const index = Tiles.tile_index(z, x, y) - new PerLayerFeatureSourceSplitter( - this.filteredLayers, - this.handleTile, - new StaticFeatureSource(geojson.features), - { - tileIndex: index, - } - ) - if (this.options.markTileVisited) { - this.options.markTileVisited(index) - } + this.registerFeatures(features) } catch (e) { console.error( "PANIC: got the tile from the OSM-api, but something crashed handling this tile" @@ -202,10 +172,12 @@ export default class OsmFeatureSource { if (e === "rate limited") { return } - await this.LoadTile(z + 1, x * 2, y * 2) - await this.LoadTile(z + 1, 1 + x * 2, y * 2) - await this.LoadTile(z + 1, x * 2, 1 + y * 2) - await this.LoadTile(z + 1, 1 + x * 2, 1 + y * 2) + await Promise.all([ + this.LoadTile(z + 1, x * 2, y * 2), + this.LoadTile(z + 1, 1 + x * 2, y * 2), + this.LoadTile(z + 1, x * 2, 1 + y * 2), + this.LoadTile(z + 1, 1 + x * 2, 1 + y * 2), + ]) } if (error !== undefined) { diff --git a/Logic/FeatureSource/TiledFeatureSource/TileHierarchy.ts b/Logic/FeatureSource/TiledFeatureSource/TileHierarchy.ts index 93d883c7e9..c318fe5e1d 100644 --- a/Logic/FeatureSource/TiledFeatureSource/TileHierarchy.ts +++ b/Logic/FeatureSource/TiledFeatureSource/TileHierarchy.ts @@ -1,7 +1,7 @@ import FeatureSource, { Tiled } from "../FeatureSource" import { BBox } from "../../BBox" -export default interface TileHierarchy { +export default interface TileHierarchy { /** * A mapping from 'tile_index' to the actual tile featrues */ diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 68fb03a3de..ae59c95c08 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -1,8 +1,9 @@ import SimpleMetaTaggers, { SimpleMetaTagger } from "./SimpleMetaTagger" import { ExtraFuncParams, ExtraFunctions } from "./ExtraFunctions" import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import { ElementStorage } from "./ElementStorage" import { Feature } from "geojson" +import FeaturePropertiesStore from "./FeatureSource/Actors/FeaturePropertiesStore" +import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" /** * Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ... @@ -12,7 +13,7 @@ import { Feature } from "geojson" export default class MetaTagging { private static errorPrintCount = 0 private static readonly stopErrorOutputAt = 10 - private static retaggingFuncCache = new Map void)[]>() + private static retaggingFuncCache = new Map void)[]>() /** * This method (re)calculates all metatags and calculated tags on every given object. @@ -24,7 +25,8 @@ export default class MetaTagging { features: Feature[], params: ExtraFuncParams, layer: LayerConfig, - state?: { allElements?: ElementStorage }, + layout: LayoutConfig, + featurePropertiesStores?: FeaturePropertiesStore, options?: { includeDates?: true | boolean includeNonDates?: true | boolean @@ -50,13 +52,14 @@ export default class MetaTagging { } // The calculated functions - per layer - which add the new keys - const layerFuncs = this.createRetaggingFunc(layer, state) + const layerFuncs = this.createRetaggingFunc(layer) + const state = { layout } let atLeastOneFeatureChanged = false for (let i = 0; i < features.length; i++) { - const ff = features[i] - const feature = ff + const feature = features[i] + const tags = featurePropertiesStores?.getStore(feature.properties.id) let somethingChanged = false let definedTags = new Set(Object.getOwnPropertyNames(feature.properties)) for (const metatag of metatagsToApply) { @@ -72,14 +75,19 @@ export default class MetaTagging { continue } somethingChanged = true - metatag.applyMetaTagsOnFeature(feature, layer, state) + metatag.applyMetaTagsOnFeature(feature, layer, tags, state) if (options?.evaluateStrict) { for (const key of metatag.keys) { feature.properties[key] } } } else { - const newValueAdded = metatag.applyMetaTagsOnFeature(feature, layer, state) + const newValueAdded = metatag.applyMetaTagsOnFeature( + feature, + layer, + tags, + state + ) /* Note that the expression: * `somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness)` * Is WRONG @@ -111,7 +119,7 @@ export default class MetaTagging { } if (somethingChanged) { - state?.allElements?.getEventSourceById(feature.properties.id)?.ping() + featurePropertiesStores?.getStore(feature.properties.id)?.ping() atLeastOneFeatureChanged = true } } @@ -199,20 +207,16 @@ export default class MetaTagging { /** * Creates the function which adds all the calculated tags to a feature. Called once per layer - * @param layer - * @param state - * @private */ private static createRetaggingFunc( - layer: LayerConfig, - state + layer: LayerConfig ): (params: ExtraFuncParams, feature: any) => boolean { const calculatedTags: [string, string, boolean][] = layer.calculatedTags if (calculatedTags === undefined || calculatedTags.length === 0) { return undefined } - let functions: ((feature: any) => void)[] = MetaTagging.retaggingFuncCache.get(layer.id) + let functions: ((feature: Feature) => void)[] = MetaTagging.retaggingFuncCache.get(layer.id) if (functions === undefined) { functions = MetaTagging.createFunctionsForFeature(layer.id, calculatedTags) MetaTagging.retaggingFuncCache.set(layer.id, functions) diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 67e44a4a83..300c115cb4 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -6,19 +6,18 @@ import { ChangeDescription, ChangeDescriptionTools } from "./Actions/ChangeDescr import { Utils } from "../../Utils" import { LocalStorageSource } from "../Web/LocalStorageSource" import SimpleMetaTagger from "../SimpleMetaTagger" -import FeatureSource from "../FeatureSource/FeatureSource" -import { ElementStorage } from "../ElementStorage" +import FeatureSource, { IndexedFeatureSource } from "../FeatureSource/FeatureSource" import { GeoLocationPointProperties } from "../State/GeoLocationState" import { GeoOperations } from "../GeoOperations" import { ChangesetHandler, ChangesetTag } from "./ChangesetHandler" import { OsmConnection } from "./OsmConnection" +import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" /** * Handles all changes made to OSM. * Needs an authenticator via OsmConnection */ export class Changes { - public readonly name = "Newly added features" /** * All the newly created features as featureSource + all the modified features */ @@ -26,7 +25,7 @@ export class Changes { public readonly pendingChanges: UIEventSource = LocalStorageSource.GetParsed("pending-changes", []) public readonly allChanges = new UIEventSource(undefined) - public readonly state: { allElements: ElementStorage; osmConnection: OsmConnection } + public readonly state: { allElements: IndexedFeatureSource; osmConnection: OsmConnection } public readonly extraComment: UIEventSource = new UIEventSource(undefined) private readonly historicalUserLocations: FeatureSource @@ -38,7 +37,9 @@ export class Changes { constructor( state?: { - allElements: ElementStorage + dryRun: UIEventSource + allElements: IndexedFeatureSource + featurePropertiesStore: FeaturePropertiesStore osmConnection: OsmConnection historicalUserLocations: FeatureSource }, @@ -50,8 +51,10 @@ export class Changes { // If a pending change contains a negative ID, we save that this._nextId = Math.min(-1, ...(this.pendingChanges.data?.map((pch) => pch.id) ?? [])) this.state = state - this._changesetHandler = state?.osmConnection?.CreateChangesetHandler( - state.allElements, + this._changesetHandler = new ChangesetHandler( + state.dryRun, + state.osmConnection, + state.featurePropertiesStore, this ) this.historicalUserLocations = state.historicalUserLocations @@ -187,7 +190,7 @@ export class Changes { const changedObjectCoordinates: [number, number][] = [] - const feature = this.state.allElements.ContainingFeatures.get(change.mainObjectId) + const feature = this.state.allElements.featuresById.data.get(change.mainObjectId) if (feature !== undefined) { changedObjectCoordinates.push(GeoOperations.centerpointCoordinates(feature)) } diff --git a/Logic/Osm/ChangesetHandler.ts b/Logic/Osm/ChangesetHandler.ts index 2661f94026..2276e0e71d 100644 --- a/Logic/Osm/ChangesetHandler.ts +++ b/Logic/Osm/ChangesetHandler.ts @@ -1,7 +1,6 @@ import escapeHtml from "escape-html" import UserDetails, { OsmConnection } from "./OsmConnection" import { UIEventSource } from "../UIEventSource" -import { ElementStorage } from "../ElementStorage" import Locale from "../../UI/i18n/Locale" import Constants from "../../Models/Constants" import { Changes } from "./Changes" @@ -14,12 +13,11 @@ export interface ChangesetTag { } export class ChangesetHandler { - private readonly allElements: ElementStorage + private readonly allElements: { addAlias: (id0: String, id1: string) => void } private osmConnection: OsmConnection private readonly changes: Changes private readonly _dryRun: UIEventSource private readonly userDetails: UIEventSource - private readonly auth: any private readonly backend: string /** @@ -28,20 +26,11 @@ export class ChangesetHandler { */ private readonly _remappings = new Map() - /** - * Use 'osmConnection.CreateChangesetHandler' instead - * @param dryRun - * @param osmConnection - * @param allElements - * @param changes - * @param auth - */ constructor( dryRun: UIEventSource, osmConnection: OsmConnection, - allElements: ElementStorage, - changes: Changes, - auth + allElements: { addAlias: (id0: String, id1: string) => void }, + changes: Changes ) { this.osmConnection = osmConnection this.allElements = allElements @@ -49,7 +38,6 @@ export class ChangesetHandler { this._dryRun = dryRun this.userDetails = osmConnection.userDetails this.backend = osmConnection._oauth_config.url - this.auth = auth if (dryRun) { console.log("DRYRUN ENABLED") @@ -61,7 +49,7 @@ export class ChangesetHandler { * * ChangesetHandler.removeDuplicateMetaTags([{key: "k", value: "v"}, {key: "k0", value: "v0"}, {key: "k", value:"v"}] // => [{key: "k", value: "v"}, {key: "k0", value: "v0"}] */ - public static removeDuplicateMetaTags(extraMetaTags: ChangesetTag[]): ChangesetTag[] { + private static removeDuplicateMetaTags(extraMetaTags: ChangesetTag[]): ChangesetTag[] { const r: ChangesetTag[] = [] const seen = new Set() for (const extraMetaTag of extraMetaTags) { @@ -82,7 +70,7 @@ export class ChangesetHandler { * @param rewriteIds * @private */ - static rewriteMetaTags(extraMetaTags: ChangesetTag[], rewriteIds: Map) { + private static rewriteMetaTags(extraMetaTags: ChangesetTag[], rewriteIds: Map) { let hasChange = false for (const tag of extraMetaTags) { const match = tag.key.match(/^([a-zA-Z0-9_]+):(node\/-[0-9])$/) @@ -198,7 +186,7 @@ export class ChangesetHandler { * @param rewriteIds: the mapping of ids * @param oldChangesetMeta: the metadata-object of the already existing changeset */ - public RewriteTagsOf( + private RewriteTagsOf( extraMetaTags: ChangesetTag[], rewriteIds: Map, oldChangesetMeta: { @@ -318,28 +306,14 @@ export class ChangesetHandler { } private async CloseChangeset(changesetId: number = undefined): Promise { - const self = this - return new Promise(function (resolve, reject) { - if (changesetId === undefined) { - return - } - self.auth.xhr( - { - method: "PUT", - path: "/api/0.6/changeset/" + changesetId + "/close", - }, - function (err, response) { - if (response == null) { - console.log("err", err) - } - console.log("Closed changeset ", changesetId) - resolve() - } - ) - }) + if (changesetId === undefined) { + return + } + await this.osmConnection.put("changeset/" + changesetId + "/close") + console.log("Closed changeset ", changesetId) } - async GetChangesetMeta(csId: number): Promise<{ + private async GetChangesetMeta(csId: number): Promise<{ id: number open: boolean uid: number @@ -358,34 +332,16 @@ export class ChangesetHandler { private async UpdateTags(csId: number, tags: ChangesetTag[]) { tags = ChangesetHandler.removeDuplicateMetaTags(tags) - const self = this - return new Promise(function (resolve, reject) { - tags = Utils.NoNull(tags).filter( - (tag) => - tag.key !== undefined && - tag.value !== undefined && - tag.key !== "" && - tag.value !== "" - ) - const metadata = tags.map((kv) => ``) - - self.auth.xhr( - { - method: "PUT", - path: "/api/0.6/changeset/" + csId, - options: { header: { "Content-Type": "text/xml" } }, - content: [``, metadata, ``].join(""), - }, - function (err, response) { - if (response === undefined) { - console.error("Updating the tags of changeset " + csId + " failed:", err) - reject(err) - } else { - resolve(response) - } - } - ) - }) + tags = Utils.NoNull(tags).filter( + (tag) => + tag.key !== undefined && + tag.value !== undefined && + tag.key !== "" && + tag.value !== "" + ) + const metadata = tags.map((kv) => ``) + const content = [``, metadata, ``].join("") + return this.osmConnection.put("changeset/" + csId, content, { "Content-Type": "text/xml" }) } private defaultChangesetTags(): ChangesetTag[] { @@ -413,57 +369,35 @@ export class ChangesetHandler { * @constructor * @private */ - private OpenChangeset(changesetTags: ChangesetTag[]): Promise { - const self = this - return new Promise(function (resolve, reject) { - const metadata = changesetTags - .map((cstag) => [cstag.key, cstag.value]) - .filter((kv) => (kv[1] ?? "") !== "") - .map((kv) => ``) - .join("\n") + private async OpenChangeset(changesetTags: ChangesetTag[]): Promise { + const metadata = changesetTags + .map((cstag) => [cstag.key, cstag.value]) + .filter((kv) => (kv[1] ?? "") !== "") + .map((kv) => ``) + .join("\n") - self.auth.xhr( - { - method: "PUT", - path: "/api/0.6/changeset/create", - options: { header: { "Content-Type": "text/xml" } }, - content: [``, metadata, ``].join(""), - }, - function (err, response) { - if (response === undefined) { - console.error("Opening a changeset failed:", err) - reject(err) - } else { - resolve(Number(response)) - } - } - ) - }) + const csId = await this.osmConnection.put( + "changeset/create", + [``, metadata, ``].join(""), + { "Content-Type": "text/xml" } + ) + return Number(csId) } /** * Upload a changesetXML */ - private UploadChange(changesetId: number, changesetXML: string): Promise> { - const self = this - return new Promise(function (resolve, reject) { - self.auth.xhr( - { - method: "POST", - options: { header: { "Content-Type": "text/xml" } }, - path: "/api/0.6/changeset/" + changesetId + "/upload", - content: changesetXML, - }, - function (err, response) { - if (response == null) { - console.error("Uploading an actual change failed", err) - reject(err) - } - const changes = self.parseUploadChangesetResponse(response) - console.log("Uploaded changeset ", changesetId) - resolve(changes) - } - ) - }) + private async UploadChange( + changesetId: number, + changesetXML: string + ): Promise> { + const response = await this.osmConnection.post( + "changeset/" + changesetId + "/upload", + changesetXML, + { "Content-Type": "text/xml" } + ) + const changes = this.parseUploadChangesetResponse(response) + console.log("Uploaded changeset ", changesetId) + return changes } } diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index f4b5c323c9..70cc9f89db 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -1,13 +1,8 @@ import osmAuth from "osm-auth" import { Store, Stores, UIEventSource } from "../UIEventSource" import { OsmPreferences } from "./OsmPreferences" -import { ChangesetHandler } from "./ChangesetHandler" -import { ElementStorage } from "../ElementStorage" -import Svg from "../../Svg" -import Img from "../../UI/Base/Img" import { Utils } from "../../Utils" import { OsmObject } from "./OsmObject" -import { Changes } from "./Changes" export default class UserDetails { public loggedIn = false @@ -148,16 +143,6 @@ export class OsmConnection { } } - public CreateChangesetHandler(allElements: ElementStorage, changes: Changes) { - return new ChangesetHandler( - this._dryRun, - /*casting is needed to make the tests work*/ this, - allElements, - changes, - this.auth - ) - } - public GetPreference( key: string, defaultValue: string = undefined, @@ -288,6 +273,57 @@ export class OsmConnection { ) } + /** + * Interact with the API. + * + * @param path: the path to query, without host and without '/api/0.6'. Example 'notes/1234/close' + */ + public async interact( + path: string, + method: "GET" | "POST" | "PUT" | "DELETE", + header?: Record, + content?: string + ): Promise { + return new Promise((ok, error) => { + this.auth.xhr( + { + method, + options: { + header, + }, + content, + path: `/api/0.6/${path}`, + }, + function (err, response) { + if (err !== null) { + error(err) + } else { + ok(response) + } + } + ) + }) + } + + public async post( + path: string, + content?: string, + header?: Record + ): Promise { + return await this.interact(path, "POST", header, content) + } + public async put( + path: string, + content?: string, + header?: Record + ): Promise { + return await this.interact(path, "PUT", header, content) + } + + public async get(path: string, header?: Record): Promise { + return await this.interact(path, "GET", header) + } + public closeNote(id: number | string, text?: string): Promise { let textSuffix = "" if ((text ?? "") !== "") { @@ -299,21 +335,7 @@ export class OsmConnection { ok() }) } - return new Promise((ok, error) => { - this.auth.xhr( - { - method: "POST", - path: `/api/0.6/notes/${id}/close${textSuffix}`, - }, - function (err, _) { - if (err !== null) { - error(err) - } else { - ok() - } - } - ) - }) + return this.post(`notes/${id}/close${textSuffix}`) } public reopenNote(id: number | string, text?: string): Promise { @@ -327,24 +349,10 @@ export class OsmConnection { if ((text ?? "") !== "") { textSuffix = "?text=" + encodeURIComponent(text) } - return new Promise((ok, error) => { - this.auth.xhr( - { - method: "POST", - path: `/api/0.6/notes/${id}/reopen${textSuffix}`, - }, - function (err, _) { - if (err !== null) { - error(err) - } else { - ok() - } - } - ) - }) + return this.post(`notes/${id}/reopen${textSuffix}`) } - public openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { + public async openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { if (this._dryRun.data) { console.warn("Dryrun enabled - not actually opening note with text ", text) return new Promise<{ id: number }>((ok) => { @@ -356,29 +364,13 @@ export class OsmConnection { } const auth = this.auth const content = { lat, lon, text } - return new Promise((ok, error) => { - auth.xhr( - { - method: "POST", - path: `/api/0.6/notes.json`, - options: { - header: { "Content-Type": "application/json" }, - }, - content: JSON.stringify(content), - }, - function (err, response: string) { - console.log("RESPONSE IS", response) - if (err !== null) { - error(err) - } else { - const parsed = JSON.parse(response) - const id = parsed.properties.id - console.log("OPENED NOTE", id) - ok({ id }) - } - } - ) + const response = await this.post("notes.json", JSON.stringify(content), { + "Content-Type": "application/json", }) + const parsed = JSON.parse(response) + const id = parsed.properties.id + console.log("OPENED NOTE", id) + return id } public async uploadGpxTrack( @@ -434,31 +426,13 @@ export class OsmConnection { } body += "--" + boundary + "--\r\n" - return new Promise((ok, error) => { - auth.xhr( - { - method: "POST", - path: `/api/0.6/gpx/create`, - options: { - header: { - "Content-Type": "multipart/form-data; boundary=" + boundary, - "Content-Length": body.length, - }, - }, - content: body, - }, - function (err, response: string) { - console.log("RESPONSE IS", response) - if (err !== null) { - error(err) - } else { - const parsed = JSON.parse(response) - console.log("Uploaded GPX track", parsed) - ok({ id: parsed }) - } - } - ) + const response = await this.post("gpx/create", body, { + "Content-Type": "multipart/form-data; boundary=" + boundary, + "Content-Length": body.length, }) + const parsed = JSON.parse(response) + console.log("Uploaded GPX track", parsed) + return { id: parsed } } public addCommentToNote(id: number | string, text: string): Promise { diff --git a/Logic/Osm/Overpass.ts b/Logic/Osm/Overpass.ts index 1293857fc8..9a4b53edbf 100644 --- a/Logic/Osm/Overpass.ts +++ b/Logic/Osm/Overpass.ts @@ -1,5 +1,4 @@ import { TagsFilter } from "../Tags/TagsFilter" -import RelationsTracker from "./RelationsTracker" import { Utils } from "../../Utils" import { ImmutableStore, Store } from "../UIEventSource" import { BBox } from "../BBox" @@ -15,14 +14,12 @@ export class Overpass { private readonly _timeout: Store private readonly _extraScripts: string[] private readonly _includeMeta: boolean - private _relationTracker: RelationsTracker constructor( filter: TagsFilter, extraScripts: string[], interpreterUrl: string, timeout?: Store, - relationTracker?: RelationsTracker, includeMeta = true ) { this._timeout = timeout ?? new ImmutableStore(90) @@ -34,7 +31,6 @@ export class Overpass { this._filter = optimized this._extraScripts = extraScripts this._includeMeta = includeMeta - this._relationTracker = relationTracker } public async queryGeoJson(bounds: BBox): Promise<[FeatureCollection, Date]> { @@ -57,7 +53,6 @@ export class Overpass { } public async ExecuteQuery(query: string): Promise<[FeatureCollection, Date]> { - const self = this const json = await Utils.downloadJson(this.buildUrl(query)) if (json.elements.length === 0 && json.remark !== undefined) { @@ -68,7 +63,6 @@ export class Overpass { console.warn("No features for", json) } - self._relationTracker?.RegisterRelations(json) const geojson = osmtogeojson(json) const osmTime = new Date(json.osm3s.timestamp_osm_base) return [geojson, osmTime] @@ -104,7 +98,6 @@ export class Overpass { /** * Constructs the actual script to execute on Overpass with geocoding * 'PostCall' can be used to set an extra range, see 'AsOverpassTurboLink' - * */ public buildScriptInArea( area: { osm_type: "way" | "relation"; osm_id: number }, @@ -142,7 +135,7 @@ export class Overpass { * Little helper method to quickly open overpass-turbo in the browser */ public static AsOverpassTurboLink(tags: TagsFilter) { - const overpass = new Overpass(tags, [], "", undefined, undefined, false) + const overpass = new Overpass(tags, [], "", undefined, false) const script = overpass.buildScript("", "({{bbox}})", true) const url = "http://overpass-turbo.eu/?Q=" return url + encodeURIComponent(script) diff --git a/Logic/Osm/RelationsTracker.ts b/Logic/Osm/RelationsTracker.ts deleted file mode 100644 index e4c65ba876..0000000000 --- a/Logic/Osm/RelationsTracker.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { UIEventSource } from "../UIEventSource" - -export interface Relation { - id: number - type: "relation" - members: { - type: "way" | "node" | "relation" - ref: number - role: string - }[] - tags: any - // Alias for tags; tags == properties - properties: any -} - -export default class RelationsTracker { - public knownRelations = new UIEventSource>( - new Map(), - "Relation memberships" - ) - - constructor() {} - - /** - * Gets an overview of the relations - except for multipolygons. We don't care about those - * @param overpassJson - * @constructor - */ - private static GetRelationElements(overpassJson: any): Relation[] { - const relations = overpassJson.elements.filter( - (element) => element.type === "relation" && element.tags.type !== "multipolygon" - ) - for (const relation of relations) { - relation.properties = relation.tags - } - return relations - } - - public RegisterRelations(overpassJson: any): void { - this.UpdateMembershipTable(RelationsTracker.GetRelationElements(overpassJson)) - } - - /** - * Build a mapping of {memberId --> {role in relation, id of relation} } - * @param relations - * @constructor - */ - private UpdateMembershipTable(relations: Relation[]): void { - const memberships = this.knownRelations.data - let changed = false - for (const relation of relations) { - for (const member of relation.members) { - const role = { - role: member.role, - relation: relation, - } - const key = member.type + "/" + member.ref - if (!memberships.has(key)) { - memberships.set(key, []) - } - const knownRelations = memberships.get(key) - - const alreadyExists = knownRelations.some((knownRole) => { - return knownRole.role === role.role && knownRole.relation === role.relation - }) - if (!alreadyExists) { - knownRelations.push(role) - changed = true - } - } - } - if (changed) { - this.knownRelations.ping() - } - } -} diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index 1a0fbb8fde..4d23d1f6c8 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -11,19 +11,145 @@ import Constants from "../Models/Constants" import { TagUtils } from "./Tags/TagUtils" import { Feature, LineString } from "geojson" import { OsmObject } from "./Osm/OsmObject" +import { OsmTags } from "../Models/OsmFeature" +import { UIEventSource } from "./UIEventSource" +import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" -export class SimpleMetaTagger { +export abstract class SimpleMetaTagger { public readonly keys: string[] public readonly doc: string public readonly isLazy: boolean public readonly includesDates: boolean - public readonly applyMetaTagsOnFeature: (feature: any, layer: LayerConfig, state) => boolean /*** * A function that adds some extra data to a feature * @param docs: what does this extra data do? - * @param f: apply the changes. Returns true if something changed */ + protected constructor(docs: { + keys: string[] + doc: string + /** + * Set this flag if the data is volatile or date-based. + * It'll _won't_ be cached in this case + */ + includesDates?: boolean + isLazy?: boolean + cleanupRetagger?: boolean + }) { + this.keys = docs.keys + this.doc = docs.doc + this.isLazy = docs.isLazy + this.includesDates = docs.includesDates ?? false + if (!docs.cleanupRetagger) { + for (const key of docs.keys) { + if (!key.startsWith("_") && key.toLowerCase().indexOf("theme") < 0) { + throw `Incorrect key for a calculated meta value '${key}': it should start with underscore (_)` + } + } + } + } + + /** + * Applies the metatag-calculation, returns 'true' if the upstream source needs to be pinged + * @param feature + * @param layer + * @param tagsStore + * @param state + */ + public abstract applyMetaTagsOnFeature( + feature: any, + layer: LayerConfig, + tagsStore: UIEventSource>, + state: { layout: LayoutConfig } + ): boolean +} + +export class ReferencingWaysMetaTagger extends SimpleMetaTagger { + /** + * Disable this metatagger, e.g. for caching or tests + * This is a bit a work-around + */ + public static enabled = true + + constructor() { + super({ + keys: ["_referencing_ways"], + isLazy: true, + doc: "_referencing_ways contains - for a node - which ways use this this node as point in their geometry. ", + }) + } + + public applyMetaTagsOnFeature(feature, layer, tags, state) { + if (!ReferencingWaysMetaTagger.enabled) { + return false + } + //this function has some extra code to make it work in SimpleAddUI.ts to also work for newly added points + const id = feature.properties.id + if (!id.startsWith("node/")) { + return false + } + console.trace("Downloading referencing ways for", feature.properties.id) + OsmObject.DownloadReferencingWays(id).then((referencingWays) => { + const currentTagsSource = state.allElements?.getEventSourceById(id) ?? [] + const wayIds = referencingWays.map((w) => "way/" + w.id) + wayIds.sort() + const wayIdsStr = wayIds.join(";") + if (wayIdsStr !== "" && currentTagsSource.data["_referencing_ways"] !== wayIdsStr) { + currentTagsSource.data["_referencing_ways"] = wayIdsStr + currentTagsSource.ping() + } + }) + + return true + } +} + +export class CountryTagger extends SimpleMetaTagger { + private static readonly coder = new CountryCoder( + Constants.countryCoderEndpoint, + Utils.downloadJson + ) + public runningTasks: Set = new Set() + + constructor() { + super({ + keys: ["_country"], + doc: "The country code of the property (with latlon2country)", + includesDates: false, + }) + } + + applyMetaTagsOnFeature(feature, _, state) { + let centerPoint: any = GeoOperations.centerpoint(feature) + const runningTasks = this.runningTasks + const lat = centerPoint.geometry.coordinates[1] + const lon = centerPoint.geometry.coordinates[0] + runningTasks.add(feature) + CountryTagger.coder + .GetCountryCodeAsync(lon, lat) + .then((countries) => { + runningTasks.delete(feature) + try { + const oldCountry = feature.properties["_country"] + feature.properties["_country"] = countries[0].trim().toLowerCase() + if (oldCountry !== feature.properties["_country"]) { + const tagsSource = state?.allElements?.getEventSourceById( + feature.properties.id + ) + tagsSource?.ping() + } + } catch (e) { + console.warn(e) + } + }) + .catch((_) => { + runningTasks.delete(feature) + }) + return false + } +} + +class InlineMetaTagger extends SimpleMetaTagger { constructor( docs: { keys: string[] @@ -36,115 +162,26 @@ export class SimpleMetaTagger { isLazy?: boolean cleanupRetagger?: boolean }, - f: (feature: any, layer: LayerConfig, state) => boolean + f: ( + feature: any, + layer: LayerConfig, + tagsStore: UIEventSource, + state: { layout: LayoutConfig } + ) => boolean ) { - this.keys = docs.keys - this.doc = docs.doc - this.isLazy = docs.isLazy + super(docs) this.applyMetaTagsOnFeature = f - this.includesDates = docs.includesDates ?? false - if (!docs.cleanupRetagger) { - for (const key of docs.keys) { - if (!key.startsWith("_") && key.toLowerCase().indexOf("theme") < 0) { - throw `Incorrect key for a calculated meta value '${key}': it should start with underscore (_)` - } - } - } } + + public readonly applyMetaTagsOnFeature: ( + feature: any, + layer: LayerConfig, + tagsStore: UIEventSource, + state: { layout: LayoutConfig } + ) => boolean } - -export class ReferencingWaysMetaTagger extends SimpleMetaTagger { - /** - * Disable this metatagger, e.g. for caching or tests - * This is a bit a work-around - */ - public static enabled = true - constructor() { - super( - { - keys: ["_referencing_ways"], - isLazy: true, - doc: "_referencing_ways contains - for a node - which ways use this this node as point in their geometry. ", - }, - (feature, _, state) => { - if (!ReferencingWaysMetaTagger.enabled) { - return false - } - //this function has some extra code to make it work in SimpleAddUI.ts to also work for newly added points - const id = feature.properties.id - if (!id.startsWith("node/")) { - return false - } - console.trace("Downloading referencing ways for", feature.properties.id) - OsmObject.DownloadReferencingWays(id).then((referencingWays) => { - const currentTagsSource = state.allElements?.getEventSourceById(id) ?? [] - const wayIds = referencingWays.map((w) => "way/" + w.id) - wayIds.sort() - const wayIdsStr = wayIds.join(";") - if ( - wayIdsStr !== "" && - currentTagsSource.data["_referencing_ways"] !== wayIdsStr - ) { - currentTagsSource.data["_referencing_ways"] = wayIdsStr - currentTagsSource.ping() - } - }) - - return true - } - ) - } -} - -export class CountryTagger extends SimpleMetaTagger { - private static readonly coder = new CountryCoder( - Constants.countryCoderEndpoint, - Utils.downloadJson - ) - public runningTasks: Set - - constructor() { - const runningTasks = new Set() - super( - { - keys: ["_country"], - doc: "The country code of the property (with latlon2country)", - includesDates: false, - }, - (feature, _, state) => { - let centerPoint: any = GeoOperations.centerpoint(feature) - const lat = centerPoint.geometry.coordinates[1] - const lon = centerPoint.geometry.coordinates[0] - runningTasks.add(feature) - CountryTagger.coder - .GetCountryCodeAsync(lon, lat) - .then((countries) => { - runningTasks.delete(feature) - try { - const oldCountry = feature.properties["_country"] - feature.properties["_country"] = countries[0].trim().toLowerCase() - if (oldCountry !== feature.properties["_country"]) { - const tagsSource = state?.allElements?.getEventSourceById( - feature.properties.id - ) - tagsSource?.ping() - } - } catch (e) { - console.warn(e) - } - }) - .catch((_) => { - runningTasks.delete(feature) - }) - return false - } - ) - this.runningTasks = runningTasks - } -} - export default class SimpleMetaTaggers { - public static readonly objectMetaInfo = new SimpleMetaTagger( + public static readonly objectMetaInfo = new InlineMetaTagger( { keys: [ "_last_edit:contributor", @@ -180,7 +217,7 @@ export default class SimpleMetaTaggers { } ) public static country = new CountryTagger() - public static geometryType = new SimpleMetaTagger( + public static geometryType = new InlineMetaTagger( { keys: ["_geometry:type"], doc: "Adds the geometry type as property. This is identical to the GoeJson geometry type and is one of `Point`,`LineString`, `Polygon` and exceptionally `MultiPolygon` or `MultiLineString`", @@ -191,6 +228,7 @@ export default class SimpleMetaTaggers { return changed } ) + public static referencingWays = new ReferencingWaysMetaTagger() private static readonly cardinalDirections = { N: 0, NNE: 22.5, @@ -209,7 +247,7 @@ export default class SimpleMetaTaggers { NW: 315, NNW: 337.5, } - private static latlon = new SimpleMetaTagger( + private static latlon = new InlineMetaTagger( { keys: ["_lat", "_lon"], doc: "The latitude and longitude of the point (or centerpoint in the case of a way/area)", @@ -225,13 +263,13 @@ export default class SimpleMetaTaggers { return true } ) - private static layerInfo = new SimpleMetaTagger( + private static layerInfo = new InlineMetaTagger( { doc: "The layer-id to which this feature belongs. Note that this might be return any applicable if `passAllFeatures` is defined.", keys: ["_layer"], includesDates: false, }, - (feature, _, layer) => { + (feature, layer) => { if (feature.properties._layer === layer.id) { return false } @@ -239,7 +277,7 @@ export default class SimpleMetaTaggers { return true } ) - private static noBothButLeftRight = new SimpleMetaTagger( + private static noBothButLeftRight = new InlineMetaTagger( { keys: [ "sidewalk:left", @@ -251,7 +289,7 @@ export default class SimpleMetaTaggers { includesDates: false, cleanupRetagger: true, }, - (feature, state, layer) => { + (feature, layer) => { if (!layer.lineRendering.some((lr) => lr.leftRightSensitive)) { return } @@ -259,7 +297,7 @@ export default class SimpleMetaTaggers { return SimpleMetaTaggers.removeBothTagging(feature.properties) } ) - private static surfaceArea = new SimpleMetaTagger( + private static surfaceArea = new InlineMetaTagger( { keys: ["_surface", "_surface:ha"], doc: "The surface area of the feature, in square meters and in hectare. Not set on points and ways", @@ -292,7 +330,7 @@ export default class SimpleMetaTaggers { return true } ) - private static levels = new SimpleMetaTagger( + private static levels = new InlineMetaTagger( { doc: "Extract the 'level'-tag into a normalized, ';'-separated value", keys: ["_level"], @@ -311,15 +349,14 @@ export default class SimpleMetaTaggers { return true } ) - - private static canonicalize = new SimpleMetaTagger( + private static canonicalize = new InlineMetaTagger( { doc: "If 'units' is defined in the layoutConfig, then this metatagger will rewrite the specified keys to have the canonical form (e.g. `1meter` will be rewritten to `1m`; `1` will be rewritten to `1m` as well)", keys: ["Theme-defined keys"], }, - (feature, _, state) => { + (feature, _, __, state) => { const units = Utils.NoNull( - [].concat(...(state?.layoutToUse?.layers?.map((layer) => layer.units) ?? [])) + [].concat(...(state?.layout?.layers?.map((layer) => layer.units) ?? [])) ) if (units.length == 0) { return @@ -369,7 +406,7 @@ export default class SimpleMetaTaggers { return rewritten } ) - private static lngth = new SimpleMetaTagger( + private static lngth = new InlineMetaTagger( { keys: ["_length", "_length:km"], doc: "The total length of a feature in meters (and in kilometers, rounded to one decimal for '_length:km'). For a surface, the length of the perimeter", @@ -383,14 +420,14 @@ export default class SimpleMetaTaggers { return true } ) - private static isOpen = new SimpleMetaTagger( + private static isOpen = new InlineMetaTagger( { keys: ["_isOpen"], doc: "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')", includesDates: true, isLazy: true, }, - (feature, _, state) => { + (feature) => { if (Utils.runningFromConsole) { // We are running from console, thus probably creating a cache // isOpen is irrelevant @@ -438,11 +475,9 @@ export default class SimpleMetaTaggers { } }, }) - - const tagsSource = state.allElements.getEventSourceById(feature.properties.id) } ) - private static directionSimplified = new SimpleMetaTagger( + private static directionSimplified = new InlineMetaTagger( { keys: ["_direction:numerical", "_direction:leftright"], doc: "_direction:numerical is a normalized, numerical direction based on 'camera:direction' or on 'direction'; it is only present if a valid direction is found (e.g. 38.5 or NE). _direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map", @@ -466,8 +501,7 @@ export default class SimpleMetaTaggers { return true } ) - - private static directionCenterpoint = new SimpleMetaTagger( + private static directionCenterpoint = new InlineMetaTagger( { keys: ["_direction:centerpoint"], isLazy: true, @@ -500,8 +534,7 @@ export default class SimpleMetaTaggers { return true } ) - - private static currentTime = new SimpleMetaTagger( + private static currentTime = new InlineMetaTagger( { keys: ["_now:date", "_now:datetime"], doc: "Adds the time that the data got loaded - pretty much the time of downloading from overpass. The format is YYYY-MM-DD hh:mm, aka 'sortable' aka ISO-8601-but-not-entirely", @@ -523,9 +556,6 @@ export default class SimpleMetaTaggers { return true } ) - - public static referencingWays = new ReferencingWaysMetaTagger() - public static metatags: SimpleMetaTagger[] = [ SimpleMetaTaggers.latlon, SimpleMetaTaggers.layerInfo, @@ -543,9 +573,6 @@ export default class SimpleMetaTaggers { SimpleMetaTaggers.levels, SimpleMetaTaggers.referencingWays, ] - public static readonly lazyTags: string[] = [].concat( - ...SimpleMetaTaggers.metatags.filter((tagger) => tagger.isLazy).map((tagger) => tagger.keys) - ) /** * Edits the given object to rewrite 'both'-tagging into a 'left-right' tagging scheme. diff --git a/Logic/State/FeaturePipelineState.ts b/Logic/State/FeaturePipelineState.ts index 429952f379..9de67d4c12 100644 --- a/Logic/State/FeaturePipelineState.ts +++ b/Logic/State/FeaturePipelineState.ts @@ -1,34 +1,21 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import FeaturePipeline from "../FeatureSource/FeaturePipeline" import { Tiles } from "../../Models/TileRange" -import { TileHierarchyAggregator } from "../../UI/ShowDataLayer/TileHierarchyAggregator" -import { UIEventSource } from "../UIEventSource" -import MapState from "./MapState" import SelectedFeatureHandler from "../Actors/SelectedFeatureHandler" import Hash from "../Web/Hash" import { BBox } from "../BBox" -import FeatureInfoBox from "../../UI/Popup/FeatureInfoBox" import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource" import MetaTagRecalculator from "../FeatureSource/Actors/MetaTagRecalculator" -import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import ShowDataLayer from "../../UI/Map/ShowDataLayer" export default class FeaturePipelineState { /** * The piece of code which fetches data from various sources and shows it on the background map */ public readonly featurePipeline: FeaturePipeline - private readonly featureAggregator: TileHierarchyAggregator private readonly metatagRecalculator: MetaTagRecalculator - private readonly popups: Map = new Map< - string, - ScrollableFullScreen - >() constructor(layoutToUse: LayoutConfig) { const clustering = layoutToUse?.clustering - this.featureAggregator = TileHierarchyAggregator.createHierarchy(this) const clusterCounter = this.featureAggregator const self = this @@ -58,7 +45,7 @@ export default class FeaturePipelineState { ) // Do show features indicates if the respective 'showDataLayer' should be shown. It can be hidden by e.g. clustering - const doShowFeatures = source.features.map( + source.features.map( (f) => { const z = self.locationControl.data.zoom @@ -112,14 +99,6 @@ export default class FeaturePipelineState { }, [self.currentBounds, source.layer.isDisplayed, sourceBBox] ) - - new ShowDataLayer(self.maplibreMap, { - features: source, - layer: source.layer.layerDef, - doShowLayer: doShowFeatures, - selectedElement: self.selectedElement, - buildPopup: (tags, layer) => self.CreatePopup(tags, layer), - }) } this.featurePipeline = new FeaturePipeline(registerSource, this, { @@ -132,13 +111,4 @@ export default class FeaturePipelineState { new SelectedFeatureHandler(Hash.hash, this) } - - public CreatePopup(tags: UIEventSource, layer: LayerConfig): ScrollableFullScreen { - if (this.popups.has(tags.data.id)) { - return this.popups.get(tags.data.id) - } - const popup = new FeatureInfoBox(tags, layer, this) - this.popups.set(tags.data.id, popup) - return popup - } } diff --git a/Models/MapProperties.ts b/Models/MapProperties.ts index 9ac228f574..39ac125c2f 100644 --- a/Models/MapProperties.ts +++ b/Models/MapProperties.ts @@ -7,8 +7,6 @@ export interface MapProperties { readonly zoom: UIEventSource readonly bounds: Store readonly rasterLayer: UIEventSource - readonly maxbounds: UIEventSource - readonly allowMoving: UIEventSource } diff --git a/Models/ThemeConfig/DependencyCalculator.ts b/Models/ThemeConfig/DependencyCalculator.ts index 1e305a036c..87dd961871 100644 --- a/Models/ThemeConfig/DependencyCalculator.ts +++ b/Models/ThemeConfig/DependencyCalculator.ts @@ -94,7 +94,6 @@ export default class DependencyCalculator { return [] }, - memberships: undefined, } // Init the extra patched functions... ExtraFunctions.FullPatchFeature(params, obj) diff --git a/Models/ThemeConfig/Json/LayerConfigJson.ts b/Models/ThemeConfig/Json/LayerConfigJson.ts index 67a6f5571a..c6b7a0199d 100644 --- a/Models/ThemeConfig/Json/LayerConfigJson.ts +++ b/Models/ThemeConfig/Json/LayerConfigJson.ts @@ -42,35 +42,19 @@ export interface LayerConfigJson { * * Note: a source must always be defined. 'special' is only allowed if this is a builtin-layer */ - source: "special" | "special:library" | ({ - /** - * Every source must set which tags have to be present in order to load the given layer. - */ - osmTags: TagConfigJson - /** - * The maximum amount of seconds that a tile is allowed to linger in the cache - */ - maxCacheAge?: number - } & ( - | { + source: + | "special" + | "special:library" + | ({ /** - * If set, this custom overpass-script will be used instead of building one by using the OSM-tags. - * Specifying OSM-tags is still obligatory and will still hide non-matching items and they will be used for the rest of the pipeline. - * _This should be really rare_. - * - * For example, when you want to fetch all grass-areas in parks and which are marked as publicly accessible: - * ``` - * "source": { - * "overpassScript": - * "way[\"leisure\"=\"park\"];node(w);is_in;area._[\"leisure\"=\"park\"];(way(area)[\"landuse\"=\"grass\"]; node(w); );", - * "osmTags": "access=yes" - * } - * ``` - * + * Every source must set which tags have to be present in order to load the given layer. */ - overpassScript?: string - } - | { + osmTags: TagConfigJson + /** + * The maximum amount of seconds that a tile is allowed to linger in the cache + */ + maxCacheAge?: number + } & { /** * The actual source of the data to load, if loaded via geojson. * @@ -104,7 +88,6 @@ export interface LayerConfigJson { */ idKey?: string }) - ) /** * diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index 599aa3fb31..db15a5df4c 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -68,6 +68,8 @@ export default class LayerConfig extends WithContextLoader { public readonly forceLoad: boolean public readonly syncSelection: typeof LayerConfig.syncSelectionAllowed[number] // this is a trick to conver a constant array of strings into a type union of these values + public readonly _needsFullNodeDatabase = false + constructor(json: LayerConfigJson, context?: string, official: boolean = true) { context = context + "." + json.id const translationContext = "layers:" + json.id @@ -250,7 +252,7 @@ export default class LayerConfig extends WithContextLoader { | "osmbasedmap" | "historicphoto" | string - )[] + )[] if (typeof pr.preciseInput.preferredBackground === "string") { preferredBackground = [pr.preciseInput.preferredBackground] } else { diff --git a/Models/ThemeConfig/SourceConfig.ts b/Models/ThemeConfig/SourceConfig.ts index 32fb0333b8..646eaa1cc5 100644 --- a/Models/ThemeConfig/SourceConfig.ts +++ b/Models/ThemeConfig/SourceConfig.ts @@ -3,7 +3,6 @@ import { RegexTag } from "../../Logic/Tags/RegexTag" export default class SourceConfig { public osmTags?: TagsFilter - public readonly overpassScript?: string public geojsonSource?: string public geojsonZoomLevel?: number public isOsmCacheLayer: boolean @@ -68,7 +67,6 @@ export default class SourceConfig { } } this.osmTags = params.osmTags ?? new RegexTag("id", /.*/) - this.overpassScript = params.overpassScript this.geojsonSource = params.geojsonSource this.geojsonZoomLevel = params.geojsonSourceLevel this.isOsmCacheLayer = params.isOsmCache ?? false diff --git a/Models/TileRange.ts b/Models/TileRange.ts index 2454ed4718..a23b459663 100644 --- a/Models/TileRange.ts +++ b/Models/TileRange.ts @@ -1,3 +1,5 @@ +import { BBox } from "../Logic/BBox" + export interface TileRange { xstart: number ystart: number @@ -85,6 +87,16 @@ export class Tiles { return { x: Tiles.lon2tile(lon, z), y: Tiles.lat2tile(lat, z), z: z } } + static tileRangeFrom(bbox: BBox, zoomlevel: number) { + return Tiles.TileRangeBetween( + zoomlevel, + bbox.getNorth(), + bbox.getWest(), + bbox.getSouth(), + bbox.getEast() + ) + } + static TileRangeBetween( zoomlevel: number, lat0: number, diff --git a/UI/AllThemesGui.ts b/UI/AllThemesGui.ts index b71df13899..c55e188c04 100644 --- a/UI/AllThemesGui.ts +++ b/UI/AllThemesGui.ts @@ -5,28 +5,32 @@ import MoreScreen from "./BigComponents/MoreScreen" import Translations from "./i18n/Translations" import Constants from "../Models/Constants" import { Utils } from "../Utils" -import LanguagePicker1 from "./LanguagePicker" +import LanguagePicker from "./LanguagePicker" import IndexText from "./BigComponents/IndexText" -import FeaturedMessage from "./BigComponents/FeaturedMessage" import { ImportViewerLinks } from "./BigComponents/UserInformation" import { LoginToggle } from "./Popup/LoginButton" +import { ImmutableStore } from "../Logic/UIEventSource" +import { OsmConnection } from "../Logic/Osm/OsmConnection" export default class AllThemesGui { setup() { try { new FixedUiElement("").AttachTo("centermessage") - const state = new UserRelatedState(undefined) + const osmConnection = new OsmConnection() + const state = new UserRelatedState(osmConnection) const intro = new Combine([ - new LanguagePicker1(Translations.t.index.title.SupportedLanguages(), "").SetClass( + new LanguagePicker(Translations.t.index.title.SupportedLanguages(), "").SetClass( "flex absolute top-2 right-3" ), new IndexText(), ]) new Combine([ intro, - new FeaturedMessage().SetClass("mb-4 block"), new MoreScreen(state, true), - new LoginToggle(undefined, Translations.t.index.logIn, state), + new LoginToggle(undefined, Translations.t.index.logIn, { + osmConnection, + featureSwitchUserbadge: new ImmutableStore(true), + }), new ImportViewerLinks(state.osmConnection), Translations.t.general.aboutMapcomplete .Subs({ osmcha_link: Utils.OsmChaLinkFor(7) }) diff --git a/UI/BigComponents/FeaturedMessage.ts b/UI/BigComponents/FeaturedMessage.ts deleted file mode 100644 index e1be38f7c6..0000000000 --- a/UI/BigComponents/FeaturedMessage.ts +++ /dev/null @@ -1,103 +0,0 @@ -import Combine from "../Base/Combine" -import welcome_messages from "../../assets/welcome_message.json" -import BaseUIElement from "../BaseUIElement" -import { FixedUiElement } from "../Base/FixedUiElement" -import MoreScreen from "./MoreScreen" -import themeOverview from "../../assets/generated/theme_overview.json" -import Translations from "../i18n/Translations" -import Title from "../Base/Title" - -export default class FeaturedMessage extends Combine { - constructor() { - const now = new Date() - let welcome_message = undefined - for (const wm of FeaturedMessage.WelcomeMessages()) { - if (wm.start_date >= now) { - continue - } - if (wm.end_date <= now) { - continue - } - - if (welcome_message !== undefined) { - console.warn("Multiple applicable messages today:", welcome_message.featured_theme) - } - welcome_message = wm - } - welcome_message = welcome_message ?? undefined - - super([FeaturedMessage.CreateFeaturedBox(welcome_message)]) - } - - public static WelcomeMessages(): { - start_date: Date - end_date: Date - message: string - featured_theme?: string - }[] { - const all_messages: { - start_date: Date - end_date: Date - message: string - featured_theme?: string - }[] = [] - - const themesById = new Map() - for (const theme of themeOverview) { - themesById.set(theme.id, theme) - } - - for (const i in welcome_messages) { - if (isNaN(Number(i))) { - continue - } - const wm = welcome_messages[i] - if (wm === null) { - continue - } - if (themesById.get(wm.featured_theme) === undefined) { - console.log("THEMES BY ID:", themesById) - console.error("Unkown featured theme for ", wm) - continue - } - - if (!wm.message) { - console.error("Featured message is missing for", wm) - continue - } - - all_messages.push({ - start_date: new Date(wm.start_date), - end_date: new Date(wm.end_date), - message: wm.message, - featured_theme: wm.featured_theme, - }) - } - return all_messages - } - - public static CreateFeaturedBox(welcome_message: { - message: string - featured_theme?: string - }): BaseUIElement { - const els: BaseUIElement[] = [] - if (welcome_message === undefined) { - return undefined - } - const title = new Title(Translations.t.index.featuredThemeTitle.Clone()) - const msg = new FixedUiElement(welcome_message.message).SetClass("link-underline font-lg") - els.push(new Combine([title, msg]).SetClass("m-4")) - if (welcome_message.featured_theme !== undefined) { - const theme = themeOverview.filter((th) => th.id === welcome_message.featured_theme)[0] - - els.push( - MoreScreen.createLinkButton({}, theme) - .SetClass("m-4 self-center md:w-160") - .SetStyle("height: min-content;") - ) - } - return new Combine(els).SetClass( - "border-2 border-grey-400 rounded-xl flex flex-col md:flex-row" - ) - } -} diff --git a/UI/ImportFlow/CompareToAlreadyExistingNotes.ts b/UI/ImportFlow/CompareToAlreadyExistingNotes.ts index 56cc4932d3..fc69ef53de 100644 --- a/UI/ImportFlow/CompareToAlreadyExistingNotes.ts +++ b/UI/ImportFlow/CompareToAlreadyExistingNotes.ts @@ -7,7 +7,6 @@ import CreateNoteImportLayer from "../../Models/ThemeConfig/Conversion/CreateNot import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" import GeoJsonSource from "../../Logic/FeatureSource/Sources/GeoJsonSource" import MetaTagging from "../../Logic/MetaTagging" -import RelationsTracker from "../../Logic/Osm/RelationsTracker" import FilteringFeatureSource from "../../Logic/FeatureSource/Sources/FilteringFeatureSource" import Minimap from "../Base/Minimap" import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" @@ -58,7 +57,6 @@ export class CompareToAlreadyExistingNotes MetaTagging.addMetatags( f, { - memberships: new RelationsTracker(), getFeaturesWithin: () => [], getFeatureById: () => undefined, }, diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index 2b2f4053f1..a4e6c15975 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -7,7 +7,6 @@ import { BBox } from "../../Logic/BBox" import { MapProperties } from "../../Models/MapProperties" import SvelteUIElement from "../Base/SvelteUIElement" import MaplibreMap from "./MaplibreMap.svelte" -import Constants from "../../Models/Constants" /** * The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties` @@ -51,7 +50,7 @@ export class MapLibreAdaptor implements MapProperties { }) this.maxbounds = state?.maxbounds ?? new UIEventSource(undefined) this.allowMoving = state?.allowMoving ?? new UIEventSource(true) - this._bounds = new UIEventSource(BBox.global) + this._bounds = new UIEventSource(undefined) this.bounds = this._bounds this.rasterLayer = state?.rasterLayer ?? new UIEventSource(undefined) @@ -75,6 +74,12 @@ export class MapLibreAdaptor implements MapProperties { dt.lat = map.getCenter().lat this.location.ping() this.zoom.setData(Math.round(map.getZoom() * 10) / 10) + const bounds = map.getBounds() + const bbox = new BBox([ + [bounds.getEast(), bounds.getNorth()], + [bounds.getWest(), bounds.getSouth()], + ]) + self._bounds.setData(bbox) }) }) diff --git a/UI/Map/ShowDataLayer.ts b/UI/Map/ShowDataLayer.ts index 2f2d820a79..5694ae2d69 100644 --- a/UI/Map/ShowDataLayer.ts +++ b/UI/Map/ShowDataLayer.ts @@ -1,6 +1,6 @@ import { ImmutableStore, Store } from "../../Logic/UIEventSource" import type { Map as MlMap } from "maplibre-gl" -import { Marker } from "maplibre-gl" +import { GeoJSONSource, Marker } from "maplibre-gl" import { ShowDataLayerOptions } from "./ShowDataLayerOptions" import { GeoOperations } from "../../Logic/GeoOperations" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" @@ -19,7 +19,7 @@ class PointRenderingLayer { private readonly _config: PointRenderingConfig private readonly _fetchStore?: (id: string) => Store private readonly _map: MlMap - private readonly _onClick: (id: string) => void + private readonly _onClick: (feature: Feature) => void private readonly _allMarkers: Map = new Map() constructor( @@ -28,7 +28,7 @@ class PointRenderingLayer { config: PointRenderingConfig, visibility?: Store, fetchStore?: (id: string) => Store, - onClick?: (id: string) => void + onClick?: (feature: Feature) => void ) { this._config = config this._map = map @@ -109,7 +109,7 @@ class PointRenderingLayer { if (this._onClick) { const self = this el.addEventListener("click", function () { - self._onClick(feature.properties.id) + self._onClick(feature) }) } @@ -144,7 +144,7 @@ class LineRenderingLayer { private readonly _config: LineRenderingConfig private readonly _visibility?: Store private readonly _fetchStore?: (id: string) => Store - private readonly _onClick?: (id: string) => void + private readonly _onClick?: (feature: Feature) => void private readonly _layername: string private readonly _listenerInstalledOn: Set = new Set() @@ -155,7 +155,7 @@ class LineRenderingLayer { config: LineRenderingConfig, visibility?: Store, fetchStore?: (id: string) => Store, - onClick?: (id: string) => void + onClick?: (feature: Feature) => void ) { this._layername = layername this._map = map @@ -174,20 +174,17 @@ class LineRenderingLayer { const config = this._config for (const key of LineRenderingLayer.lineConfigKeys) { - const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt - calculatedProps[key] = v + calculatedProps[key] = config[key]?.GetRenderValue(properties)?.Subs(properties).txt } for (const key of LineRenderingLayer.lineConfigKeysColor) { let v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt if (v === undefined) { continue } - console.log("Color", v) if (v.length == 9 && v.startsWith("#")) { // This includes opacity calculatedProps[key + "-opacity"] = parseInt(v.substring(7), 16) / 256 v = v.substring(0, 7) - console.log("Color >", v, calculatedProps[key + "-opacity"]) } calculatedProps[key] = v } @@ -196,7 +193,6 @@ class LineRenderingLayer { calculatedProps[key] = Number(v) } - console.log("Calculated props:", calculatedProps, "for", properties.id) return calculatedProps } @@ -205,52 +201,53 @@ class LineRenderingLayer { while (!map.isStyleLoaded()) { await Utils.waitFor(100) } - map.addSource(this._layername, { - type: "geojson", - data: { + const src = map.getSource(this._layername) + if (src === undefined) { + map.addSource(this._layername, { + type: "geojson", + data: { + type: "FeatureCollection", + features, + }, + promoteId: "id", + }) + // @ts-ignore + map.addLayer({ + source: this._layername, + id: this._layername + "_line", + type: "line", + paint: { + "line-color": ["feature-state", "color"], + "line-opacity": ["feature-state", "color-opacity"], + "line-width": ["feature-state", "width"], + "line-offset": ["feature-state", "offset"], + }, + layout: { + "line-cap": "round", + }, + }) + + map.addLayer({ + source: this._layername, + id: this._layername + "_polygon", + type: "fill", + filter: ["in", ["geometry-type"], ["literal", ["Polygon", "MultiPolygon"]]], + layout: {}, + paint: { + "fill-color": ["feature-state", "fillColor"], + "fill-opacity": 0.1, + }, + }) + } else { + src.setData({ type: "FeatureCollection", features, - }, - promoteId: "id", - }) - - map.addLayer({ - source: this._layername, - id: this._layername + "_line", - type: "line", - paint: { - "line-color": ["feature-state", "color"], - "line-opacity": ["feature-state", "color-opacity"], - "line-width": ["feature-state", "width"], - "line-offset": ["feature-state", "offset"], - }, - }) - - /*[ - "color", - "width", - "dashArray", - "lineCap", - "offset", - "fill", - "fillColor", - ]*/ - map.addLayer({ - source: this._layername, - id: this._layername + "_polygon", - type: "fill", - filter: ["in", ["geometry-type"], ["literal", ["Polygon", "MultiPolygon"]]], - layout: {}, - paint: { - "fill-color": ["feature-state", "fillColor"], - "fill-opacity": 0.1, - }, - }) + }) + } for (let i = 0; i < features.length; i++) { const feature = features[i] const id = feature.properties.id ?? feature.id - console.log("ID is", id) if (id === undefined) { console.trace( "Got a feature without ID; this causes rendering bugs:", @@ -310,23 +307,6 @@ export default class ShowDataLayer { }) } - private openOrReusePopup(id: string): void { - if (!this._popupCache || !this._options.fetchStore) { - return - } - if (this._popupCache.has(id)) { - this._popupCache.get(id).Activate() - return - } - const tags = this._options.fetchStore(id) - if (!tags) { - return - } - const popup = this._options.buildPopup(tags, this._options.layer) - this._popupCache.set(id, popup) - popup.Activate() - } - private zoomToCurrentFeatures(map: MlMap) { if (this._options.zoomToFeatures) { const features = this._options.features.features.data @@ -338,8 +318,8 @@ export default class ShowDataLayer { } private initDrawFeatures(map: MlMap) { - const { features, doShowLayer, fetchStore, buildPopup } = this._options - const onClick = buildPopup === undefined ? undefined : (id) => this.openOrReusePopup(id) + const { features, doShowLayer, fetchStore, selectedElement } = this._options + const onClick = (feature: Feature) => selectedElement?.setData(feature) for (let i = 0; i < this._options.layer.lineRendering.length; i++) { const lineRenderingConfig = this._options.layer.lineRendering[i] new LineRenderingLayer( diff --git a/UI/Map/ShowDataLayerOptions.ts b/UI/Map/ShowDataLayerOptions.ts index dde88b6631..524e9847c9 100644 --- a/UI/Map/ShowDataLayerOptions.ts +++ b/UI/Map/ShowDataLayerOptions.ts @@ -1,8 +1,5 @@ import FeatureSource from "../../Logic/FeatureSource/FeatureSource" import { Store, UIEventSource } from "../../Logic/UIEventSource" -import { ElementStorage } from "../../Logic/ElementStorage" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import ScrollableFullScreen from "../Base/ScrollableFullScreen" import { OsmTags } from "../../Models/OsmFeature" export interface ShowDataLayerOptions { @@ -11,15 +8,10 @@ export interface ShowDataLayerOptions { */ features: FeatureSource /** - * Indication of the current selected element; overrides some filters + * Indication of the current selected element; overrides some filters. + * When a feature is tapped, the feature will be put in there */ selectedElement?: UIEventSource - /** - * What popup to build when a feature is selected - */ - buildPopup?: - | undefined - | ((tags: UIEventSource, layer: LayerConfig) => ScrollableFullScreen) /** * If set, zoom to the features when initially loaded and when they are changed @@ -31,7 +23,8 @@ export interface ShowDataLayerOptions { doShowLayer?: Store /** - * Function which fetches the relevant store + * Function which fetches the relevant store. + * If given, the map will update when a property is changed */ fetchStore?: (id: string) => UIEventSource } diff --git a/UI/Map/ShowDataMultiLayer.ts b/UI/Map/ShowDataMultiLayer.ts index 84710b6fc6..677e71d760 100644 --- a/UI/Map/ShowDataMultiLayer.ts +++ b/UI/Map/ShowDataMultiLayer.ts @@ -1,24 +1,35 @@ /** * SHows geojson on the given leaflet map, but attempts to figure out the correct layer first */ -import { Store } from "../../Logic/UIEventSource" +import { ImmutableStore, Store } from "../../Logic/UIEventSource" import ShowDataLayer from "./ShowDataLayer" import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter" import FilteredLayer from "../../Models/FilteredLayer" import { ShowDataLayerOptions } from "./ShowDataLayerOptions" import { Map as MlMap } from "maplibre-gl" +import FilteringFeatureSource from "../../Logic/FeatureSource/Sources/FilteringFeatureSource" +import { GlobalFilter } from "../../Models/GlobalFilter" + export default class ShowDataMultiLayer { constructor( map: Store, - options: ShowDataLayerOptions & { layers: Store } + options: ShowDataLayerOptions & { + layers: FilteredLayer[] + globalFilters?: Store + } ) { new PerLayerFeatureSourceSplitter( - options.layers, - (perLayer) => { + new ImmutableStore(options.layers), + (features, layer) => { const newOptions = { ...options, - layer: perLayer.layer.layerDef, - features: perLayer, + layer: layer.layerDef, + features: new FilteringFeatureSource( + layer, + features, + options.fetchStore, + options.globalFilters + ), } new ShowDataLayer(map, newOptions) }, diff --git a/UI/ShowDataLayer/ShowOverlayLayer.ts b/UI/ShowDataLayer/ShowOverlayLayer.ts deleted file mode 100644 index 697d5e0d1f..0000000000 --- a/UI/ShowDataLayer/ShowOverlayLayer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" -import { UIEventSource } from "../../Logic/UIEventSource" - -export default class ShowOverlayLayer { - public static implementation: ( - config: TilesourceConfig, - leafletMap: UIEventSource, - isShown?: UIEventSource - ) => void - - constructor( - config: TilesourceConfig, - leafletMap: UIEventSource, - isShown: UIEventSource = undefined - ) { - if (ShowOverlayLayer.implementation === undefined) { - throw "Call ShowOverlayLayerImplemenation.initialize() first before using this" - } - ShowOverlayLayer.implementation(config, leafletMap, isShown) - } -} diff --git a/UI/ShowDataLayer/ShowOverlayLayerImplementation.ts b/UI/ShowDataLayer/ShowOverlayLayerImplementation.ts index 82e461bbed..64a6d3ab1d 100644 --- a/UI/ShowDataLayer/ShowOverlayLayerImplementation.ts +++ b/UI/ShowDataLayer/ShowOverlayLayerImplementation.ts @@ -3,6 +3,7 @@ import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" import { UIEventSource } from "../../Logic/UIEventSource" import ShowOverlayLayer from "./ShowOverlayLayer" +// TODO port this to maplibre! export default class ShowOverlayLayerImplementation { public static Implement() { ShowOverlayLayer.implementation = ShowOverlayLayerImplementation.AddToMap diff --git a/UI/ShowDataLayer/TileHierarchyAggregator.ts b/UI/ShowDataLayer/TileHierarchyAggregator.ts deleted file mode 100644 index 71ba7addc2..0000000000 --- a/UI/ShowDataLayer/TileHierarchyAggregator.ts +++ /dev/null @@ -1,257 +0,0 @@ -import FeatureSource, { - FeatureSourceForLayer, - Tiled, -} from "../../Logic/FeatureSource/FeatureSource" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { UIEventSource } from "../../Logic/UIEventSource" -import { Tiles } from "../../Models/TileRange" -import { BBox } from "../../Logic/BBox" -import FilteredLayer from "../../Models/FilteredLayer" -import { Feature } from "geojson" - -/** - * A feature source containing but a single feature, which keeps stats about a tile - */ -export class TileHierarchyAggregator implements FeatureSource { - private static readonly empty = [] - public totalValue: number = 0 - public showCount: number = 0 - public hiddenCount: number = 0 - public readonly features = new UIEventSource(TileHierarchyAggregator.empty) - public readonly name - private _parent: TileHierarchyAggregator - private _root: TileHierarchyAggregator - private readonly _z: number - private readonly _x: number - private readonly _y: number - private readonly _tileIndex: number - private _counter: SingleTileCounter - private _subtiles: [ - TileHierarchyAggregator, - TileHierarchyAggregator, - TileHierarchyAggregator, - TileHierarchyAggregator - ] = [undefined, undefined, undefined, undefined] - private readonly featuresStatic = [] - private readonly featureProperties: { - count: string - kilocount: string - tileId: string - id: string - showCount: string - totalCount: string - } - private readonly _state: { filteredLayers: UIEventSource } - private readonly updateSignal = new UIEventSource(undefined) - - private constructor( - parent: TileHierarchyAggregator, - state: { - filteredLayers: UIEventSource - }, - z: number, - x: number, - y: number - ) { - this._parent = parent - this._state = state - this._root = parent?._root ?? this - this._z = z - this._x = x - this._y = y - this._tileIndex = Tiles.tile_index(z, x, y) - this.name = "Count(" + this._tileIndex + ")" - - const totals = { - id: "" + this._tileIndex, - tileId: "" + this._tileIndex, - count: `0`, - kilocount: "0", - showCount: "0", - totalCount: "0", - } - this.featureProperties = totals - - const now = new Date() - const feature = { - type: "Feature", - properties: totals, - geometry: { - type: "Point", - coordinates: Tiles.centerPointOf(z, x, y), - }, - } - this.featuresStatic.push({ feature: feature, freshness: now }) - - const bbox = BBox.fromTile(z, x, y) - const box = { - type: "Feature", - properties: totals, - geometry: { - type: "Polygon", - coordinates: [ - [ - [bbox.minLon, bbox.minLat], - [bbox.minLon, bbox.maxLat], - [bbox.maxLon, bbox.maxLat], - [bbox.maxLon, bbox.minLat], - [bbox.minLon, bbox.minLat], - ], - ], - }, - } - this.featuresStatic.push({ feature: box, freshness: now }) - } - - public static createHierarchy(state: { filteredLayers: UIEventSource }) { - return new TileHierarchyAggregator(undefined, state, 0, 0, 0) - } - - public getTile(tileIndex): TileHierarchyAggregator { - if (tileIndex === this._tileIndex) { - return this - } - let [tileZ, tileX, tileY] = Tiles.tile_from_index(tileIndex) - while (tileZ - 1 > this._z) { - tileX = Math.floor(tileX / 2) - tileY = Math.floor(tileY / 2) - tileZ-- - } - const xDiff = tileX - 2 * this._x - const yDiff = tileY - 2 * this._y - const subtileIndex = yDiff * 2 + xDiff - return this._subtiles[subtileIndex]?.getTile(tileIndex) - } - - public addTile(source: FeatureSourceForLayer & Tiled) { - const self = this - if (source.tileIndex === this._tileIndex) { - if (this._counter === undefined) { - this._counter = new SingleTileCounter(this._tileIndex) - this._counter.countsPerLayer.addCallbackAndRun((_) => self.update()) - } - this._counter.addTileCount(source) - } else { - // We have to give it to one of the subtiles - let [tileZ, tileX, tileY] = Tiles.tile_from_index(source.tileIndex) - while (tileZ - 1 > this._z) { - tileX = Math.floor(tileX / 2) - tileY = Math.floor(tileY / 2) - tileZ-- - } - const xDiff = tileX - 2 * this._x - const yDiff = tileY - 2 * this._y - - const subtileIndex = yDiff * 2 + xDiff - if (this._subtiles[subtileIndex] === undefined) { - this._subtiles[subtileIndex] = new TileHierarchyAggregator( - this, - this._state, - tileZ, - tileX, - tileY - ) - } - this._subtiles[subtileIndex].addTile(source) - } - this.updateSignal.setData(source) - } - private update() { - const newMap = new Map() - let total = 0 - let hiddenCount = 0 - let showCount = 0 - let isShown: Map = new Map() - for (const filteredLayer of this._state.filteredLayers.data) { - isShown.set(filteredLayer.layerDef.id, filteredLayer) - } - this?._counter?.countsPerLayer?.data?.forEach((count, layerId) => { - newMap.set("layer:" + layerId, count) - total += count - this.featureProperties["direct_layer:" + layerId] = count - const flayer = isShown.get(layerId) - if (flayer.isDisplayed.data && this._z >= flayer.layerDef.minzoom) { - showCount += count - } else { - hiddenCount += count - } - }) - - for (const tile of this._subtiles) { - if (tile === undefined) { - continue - } - total += tile.totalValue - - showCount += tile.showCount - hiddenCount += tile.hiddenCount - - for (const key in tile.featureProperties) { - if (key.startsWith("layer:")) { - newMap.set( - key, - (newMap.get(key) ?? 0) + Number(tile.featureProperties[key] ?? 0) - ) - } - } - } - - this.totalValue = total - this.showCount = showCount - this.hiddenCount = hiddenCount - this._parent?.update() - - if (total === 0) { - this.features.setData(TileHierarchyAggregator.empty) - } else { - this.featureProperties.count = "" + total - this.featureProperties.kilocount = "" + Math.floor(total / 1000) - this.featureProperties.showCount = "" + showCount - this.featureProperties.totalCount = "" + total - newMap.forEach((value, key) => { - this.featureProperties[key] = "" + value - }) - - this.features.data = this.featuresStatic - this.features.ping() - } - } -} - -/** - * Keeps track of a single tile - */ -class SingleTileCounter implements Tiled { - public readonly bbox: BBox - public readonly tileIndex: number - public readonly countsPerLayer: UIEventSource> = new UIEventSource< - Map - >(new Map()) - public readonly z: number - public readonly x: number - public readonly y: number - private readonly registeredLayers: Map = new Map() - - constructor(tileIndex: number) { - this.tileIndex = tileIndex - this.bbox = BBox.fromTileIndex(tileIndex) - const [z, x, y] = Tiles.tile_from_index(tileIndex) - this.z = z - this.x = x - this.y = y - } - - public addTileCount(source: FeatureSourceForLayer) { - const layer = source.layer.layerDef - this.registeredLayers.set(layer.id, layer) - const self = this - source.features.map( - (f) => { - const isDisplayed = source.layer.isDisplayed.data - self.countsPerLayer.data.set(layer.id, isDisplayed ? f.length : 0) - self.countsPerLayer.ping() - }, - [source.layer.isDisplayed] - ) - } -} diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index d2dcd750c9..a68e718b97 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -11,7 +11,6 @@ import { QueryParameters } from "../Logic/Web/QueryParameters"; import UserRelatedState from "../Logic/State/UserRelatedState"; import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"; - import { ElementStorage } from "../Logic/ElementStorage"; import { Changes } from "../Logic/Osm/Changes"; import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor"; import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader"; @@ -28,6 +27,12 @@ import LayerState from "../Logic/State/LayerState"; import Constants from "../Models/Constants"; import type { Feature } from "geojson"; + import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore"; + import ShowDataMultiLayer from "./Map/ShowDataMultiLayer"; + import { Or } from "../Logic/Tags/Or"; + import LayoutSource from "../Logic/FeatureSource/LayoutSource"; + import { type OsmTags } from "../Models/OsmFeature"; + export let layout: LayoutConfig; const maplibremap: UIEventSource = new UIEventSource(undefined); @@ -49,16 +54,34 @@ }); const userRelatedState = new UserRelatedState(osmConnection, layout?.language); const selectedElement = new UIEventSource(undefined, "Selected element"); + selectedElement.addCallbackAndRunD(s => console.log("Selected element:", s)) const geolocation = new GeoLocationHandler(geolocationState, selectedElement, mapproperties, userRelatedState.gpsLocationHistoryRetentionTime); - const allElements = new ElementStorage(); + const tags = new Or(layout.layers.filter(l => l.source !== null&& Constants.priviliged_layers.indexOf(l.id) < 0 && l.source.geojsonSource === undefined).map(l => l.source.osmTags )) + const layerState = new LayerState(osmConnection, layout.layers, layout.id) + + const indexedElements = new LayoutSource(layout.layers, featureSwitches, new StaticFeatureSource([]), mapproperties, osmConnection.Backend(), + (id) => layerState.filteredLayers.get(id).isDisplayed + ) + + const allElements = new FeaturePropertiesStore(indexedElements) const changes = new Changes({ - allElements, + dryRun: featureSwitches.featureSwitchIsTesting, + allElements: indexedElements, + featurePropertiesStore: allElements, osmConnection, historicalUserLocations: geolocation.historicalUserLocations }, layout?.isLeftRightSensitive() ?? false); - console.log("Setting up layerstate...") - const layerState = new LayerState(osmConnection, layout.layers, layout.id) + + new ShowDataMultiLayer(maplibremap, { + layers: Array.from(layerState.filteredLayers.values()), + features: indexedElements, + fetchStore: id => > allElements.getStore(id), + selectedElement, + globalFilters: layerState.globalFilters + }) + + { // Various actors that we don't need to reference // TODO enable new TitleHandler(selectedElement,layout,allElements) @@ -98,7 +121,7 @@ current_view: new StaticFeatureSource(mapproperties.bounds.map(bbox => bbox === undefined ? empty : [bbox.asGeoJson({id:"current_view"})])), } layerState.filteredLayers.get("range")?.isDisplayed?.syncWith(featureSwitches.featureSwitchIsTesting, true) -console.log("RAnge fs", specialLayers.range) + specialLayers.range.features.addCallbackAndRun(fs => console.log("Range.features:", JSON.stringify(fs))) layerState.filteredLayers.forEach((flayer) => { const features = specialLayers[flayer.layerDef.id] @@ -116,7 +139,8 @@ console.log("RAnge fs", specialLayers.range) -
+
+
Hello world
diff --git a/assets/layers/grass_in_parks/grass_in_parks.json b/assets/layers/grass_in_parks/grass_in_parks.json deleted file mode 100644 index e583384a61..0000000000 --- a/assets/layers/grass_in_parks/grass_in_parks.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "id": "grass_in_parks", - "name": { - "nl": "Toegankelijke grasvelden in parken" - }, - "source": { - "osmTags": { - "or": [ - "name=Park Oude God", - { - "and": [ - "landuse=grass", - { - "or": [ - "access=public", - "access=yes" - ] - } - ] - } - ] - }, - "overpassScript": "way[\"leisure\"=\"park\"];node(w);is_in;area._[\"leisure\"=\"park\"];(way(area)[\"landuse\"=\"grass\"]; node(w); );" - }, - "minzoom": 0, - "title": { - "render": { - "nl": "Speelweide in een park" - }, - "mappings": [ - { - "if": "name~*", - "then": { - "nl": "{name}" - } - } - ] - }, - "tagRenderings": [ - "images", - { - "id": "explanation", - "render": "Op dit grasveld in het park mag je spelen, picnicken, zitten, ..." - }, - { - "id": "grass-in-parks-reviews", - "render": "{reviews(name, landuse=grass )}" - } - ], - "mapRendering": [ - { - "icon": "./assets/themes/playgrounds/playground.svg", - "iconSize": "40,40,center", - "location": [ - "point", - "centroid" - ] - }, - { - "color": "#0f0", - "width": "1" - } - ], - "description": { - "en": "Searches for all accessible grass patches within public parks - these are 'groenzones'", - "nl": "Dit zoekt naar alle toegankelijke grasvelden binnen publieke parken - dit zijn 'groenzones'", - "de": "Sucht nach allen zugänglichen Grasflächen in öffentlichen Parks - dies sind 'Grünzonen'", - "ca": "Cerques per a tots els camins d'herba accessibles dins dels parcs públics - aquests són «groenzones»" - } -} \ No newline at end of file diff --git a/assets/themes/speelplekken/speelplekken.json b/assets/themes/speelplekken/speelplekken.json index c4049473f1..8f73719457 100644 --- a/assets/themes/speelplekken/speelplekken.json +++ b/assets/themes/speelplekken/speelplekken.json @@ -93,22 +93,6 @@ ] } }, - { - "builtin": "grass_in_parks", - "override": { - "minzoom": 14, - "source": { - "geoJsonLocal": "http://127.0.0.1:8080/speelplekken_{layer}_{z}_{x}_{y}.geojson", - "geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/speelplekken_cache/speelplekken_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 14, - "isOsmCache": true - }, - "calculatedTags": [ - "_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes': ''", - "_video:id=feat.properties.video === undefined ? undefined : new URL(feat.properties.video).searchParams.get('v')" - ] - } - }, { "builtin": "sport_pitch", "override": { @@ -129,7 +113,6 @@ "builtin": "slow_roads", "override": { "calculatedTags": [ - "_part_of_walking_routes=Array.from(new Set(feat.memberships().map(r => \"\" + r.relation.tags.name + \"\"))).join(', ')", "_is_shadowed=feat.overlapWith('shadow').length > 0 ? 'yes': ''" ], "source": { @@ -268,11 +251,6 @@ }, "overrideAll": { "+tagRenderings": [ - { - "id": "part-of-walk", - "render": "Maakt deel uit van {_part_of_walking_routes}", - "condition": "_part_of_walking_routes~*" - }, { "id": "has-video", "freeform": { @@ -289,4 +267,4 @@ ], "isShown": "_is_shadowed!=yes" } -} \ No newline at end of file +} diff --git a/assets/welcome_message.json b/assets/welcome_message.json deleted file mode 100644 index 31f736510a..0000000000 --- a/assets/welcome_message.json +++ /dev/null @@ -1,61 +0,0 @@ -[ - { - "start_date": "2022-05-30", - "end_date":"2022-06-05", - "message": "The 3rd of June is World Bicycle DayX. Go find a bike shop or bike pump nearby", - "featured_theme": "cyclofix" - }, - { - "start_date": "2022-04-24", - "end_date": "2022-05-30", - "message": "Help translating MapComplete! If you have some free time, please translate MapComplete to your favourite language. Read the instructions here" - }, - { - "start_date": "2022-04-18", - "end_date": "2022-04-24", - "message": "The 23rd of april is World Book Day. Go grab a book in a public bookcase (which is a piece of street furniture containing books where books can be taken and exchanged). Or alternative, search and map all of them in your neighbourhood!", - "featured_theme": "bookcases" - }, - { - "start_date": "2022-04-11", - "end_date": "2022-04-18", - "message": "The 15th of april is World Art Day - the ideal moment to go out, enjoy some artwork and add missing artwork to the map. And of course, you can snap some pictures", - "featured_theme": "artwork" - }, - { - "start_date": "2022-03-24", - "end_date": "2022-03-31", - "message": "The 22nd of March is World Water Day. Time to go out and find all the public drinking water spots!", - "featured_theme": "drinking_water" - }, - { - "start_date": "2022-01-24", - "end_date": "2022-01-30", - "message": "The 28th of January is International Privacy Day. Do you want to know where all the surveillance cameras are? Go find out!", - "featured_theme": "surveillance" - }, - { - "start_date": "2021-12-27", - "end_date": "2021-12-30", - "message": "In more normal circumstances, there would be a very cool gathering in Leipzig around this time with thousands of tech-minded people. However, due to some well-known circumstances, it is a virtual-only event this year as well. However, there might be a local hackerspace nearby to fill in this void", - "featured_theme": "hackerspaces" - }, - { - "start_date": "2021-11-01", - "end_date": "2021-11-07", - "message": "The first days of november is, in many European traditions, a moment that we remember our deceased. That is why this week the ghost bikes are featured. A ghost bike is a memorial in the form of a bicycle painted white which is placed to remember a cyclist whom was killed in a traffic accident. The ghostbike-theme shows such memorials. Even though there are already too much such memorials on the map, please add missing ones if you encounter them.", - "featured_theme": "ghostbikes" - }, - { - "start_date": "2021-10-25", - "end_date": "2021-11-01", - "message": "Did you know you could link OpenStreetMap with Wikidata? With name:etymology:wikidata, it is even possible to link to whom or what a feature is named after. Quite some volunteers have done this - because it is interesting or for the Equal Street Names-project. For this, a new theme has been created which shows the Wikipedia page and Wikimedia-images of this tag and which makes it easy to link them both with the search box. Give it a try!", - "featured_theme": "etymology" - }, - { - "start_date": "2021-10-17", - "end_date": "2021-10-25", - "message": "

Hi all!

Thanks for using MapComplete. It has been quite a ride since it's inception, a bit over a year ago. MapComplete has grown significantly recently, which you can read more about on in my diary entry.

Furthermore, NicoleLaine made a really cool new theme about postboxes, so make sure to check it out!

", - "featured_theme": "postboxes" - } -] \ No newline at end of file diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index a518e8e702..d563745b40 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -1443,11 +1443,6 @@ video { border-color: rgb(219 234 254 / var(--tw-border-opacity)); } -.border-red-500 { - --tw-border-opacity: 1; - border-color: rgb(239 68 68 / var(--tw-border-opacity)); -} - .border-gray-300 { --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); @@ -1873,6 +1868,12 @@ video { transition-duration: 150ms; } +.transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + .transition-\[color\2c background-color\2c box-shadow\] { transition-property: color,background-color,box-shadow; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); diff --git a/scripts/generateCache.ts b/scripts/generateCache.ts index 24ff393b9f..052008af3e 100644 --- a/scripts/generateCache.ts +++ b/scripts/generateCache.ts @@ -7,7 +7,6 @@ import { existsSync, readFileSync, writeFileSync } from "fs" import { TagsFilter } from "../Logic/Tags/TagsFilter" import { Or } from "../Logic/Tags/Or" import { AllKnownLayouts } from "../Customizations/AllKnownLayouts" -import RelationsTracker from "../Logic/Osm/RelationsTracker" import * as OsmToGeoJson from "osmtogeojson" import MetaTagging from "../Logic/MetaTagging" import { ImmutableStore, UIEventSource } from "../Logic/UIEventSource" @@ -26,13 +25,11 @@ import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeat import Loc from "../Models/Loc" import { Feature } from "geojson" import { BBox } from "../Logic/BBox" -import { bboxClip } from "@turf/turf" ScriptUtils.fixUtils() function createOverpassObject( theme: LayoutConfig, - relationTracker: RelationsTracker, backend: string ) { let filters: TagsFilter[] = [] @@ -52,12 +49,7 @@ function createOverpassObject( } } - // Check if data for this layer has already been loaded - if (layer.source.overpassScript !== undefined) { - extraScripts.push(layer.source.overpassScript) - } else { - filters.push(layer.source.osmTags) - } + filters.push(layer.source.osmTags) } filters = Utils.NoNull(filters) extraScripts = Utils.NoNull(extraScripts) @@ -69,7 +61,6 @@ function createOverpassObject( extraScripts, backend, new UIEventSource(60), - relationTracker ) } @@ -86,7 +77,6 @@ async function downloadRaw( targetdir: string, r: TileRange, theme: LayoutConfig, - relationTracker: RelationsTracker ): Promise<{ failed: number; skipped: number }> { let downloaded = 0 let failed = 0 @@ -130,7 +120,6 @@ async function downloadRaw( } const overpass = createOverpassObject( theme, - relationTracker, Constants.defaultOverpassUrls[failed % Constants.defaultOverpassUrls.length] ) const url = overpass.buildQuery( @@ -233,7 +222,6 @@ function loadAllTiles( function sliceToTiles( allFeatures: FeatureSource, theme: LayoutConfig, - relationsTracker: RelationsTracker, targetdir: string, pointsOnlyLayers: string[], clip: boolean @@ -244,8 +232,7 @@ function sliceToTiles( let indexisBuilt = false function buildIndex() { - for (const ff of allFeatures.features.data) { - const f = ff.feature + for (const f of allFeatures.features.data) { indexedFeatures.set(f.properties.id, f) } indexisBuilt = true @@ -281,9 +268,8 @@ function sliceToTiles( MetaTagging.addMetatags( source.features.data, { - memberships: relationsTracker, getFeaturesWithin: (_) => { - return [allFeatures.features.data.map((f) => f.feature)] + return [allFeatures.features.data] }, getFeatureById: getFeatureById, }, @@ -348,7 +334,7 @@ function sliceToTiles( } let strictlyCalculated = 0 let featureCount = 0 - let features: Feature[] = filteredTile.features.data.map((f) => f.feature) + let features: Feature[] = filteredTile.features.data for (const feature of features) { // Some cleanup @@ -444,7 +430,7 @@ function sliceToTiles( source, new UIEventSource(undefined) ) - const features = filtered.features.data.map((f) => f.feature) + const features = filtered.features.data const points = features.map((feature) => GeoOperations.centerpoint(feature)) console.log("Writing points overview for ", layerId) @@ -571,11 +557,9 @@ export async function main(args: string[]) { } } - const relationTracker = new RelationsTracker() - let failed = 0 do { - const cachingResult = await downloadRaw(targetdir, tileRange, theme, relationTracker) + const cachingResult = await downloadRaw(targetdir, tileRange, theme) failed = cachingResult.failed if (failed > 0) { await ScriptUtils.sleep(30000) @@ -584,7 +568,7 @@ export async function main(args: string[]) { const extraFeatures = await downloadExtraData(theme) const allFeaturesSource = loadAllTiles(targetdir, tileRange, theme, extraFeatures) - sliceToTiles(allFeaturesSource, theme, relationTracker, targetdir, generatePointLayersFor, clip) + sliceToTiles(allFeaturesSource, theme, targetdir, generatePointLayersFor, clip) } let args = [...process.argv] diff --git a/test.ts b/test.ts index b1375d4ab6..73c93eb391 100644 --- a/test.ts +++ b/test.ts @@ -2,12 +2,13 @@ import SvelteUIElement from "./UI/Base/SvelteUIElement" import ThemeViewGUI from "./UI/ThemeViewGUI.svelte" import { FixedUiElement } from "./UI/Base/FixedUiElement" import { QueryParameters } from "./Logic/Web/QueryParameters" -import { AllKnownLayoutsLazy } from "./Customizations/AllKnownLayouts" import LayoutConfig from "./Models/ThemeConfig/LayoutConfig" import * as benches from "./assets/generated/themes/benches.json" + async function main() { new FixedUiElement("Determining layout...").AttachTo("maindiv") const qp = QueryParameters.GetQueryParameter("layout", "") + new FixedUiElement("").AttachTo("extradiv") const layout = new LayoutConfig(benches, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) console.log("Using layout", layout.id) new SvelteUIElement(ThemeViewGUI, { layout }).AttachTo("maindiv") diff --git a/test/Logic/ExtraFunctions.spec.ts b/test/Logic/ExtraFunctions.spec.ts index cb9598b08d..869ffb3598 100644 --- a/test/Logic/ExtraFunctions.spec.ts +++ b/test/Logic/ExtraFunctions.spec.ts @@ -115,7 +115,6 @@ describe("OverlapFunc", () => { const params: ExtraFuncParams = { getFeatureById: (id) => undefined, getFeaturesWithin: () => [[door]], - memberships: undefined, } ExtraFunctions.FullPatchFeature(params, hermanTeirlinck) From 5d0fe31c4192553d5f005403eddb8017c2475134 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 28 Mar 2023 05:13:48 +0200 Subject: [PATCH 007/257] refactoring --- Logic/Actors/PendingChangesUploader.ts | 3 +- Logic/Actors/TitleHandler.ts | 20 +- Logic/ContributorCount.ts | 34 +- Logic/ExtraFunctions.ts | 1 - Logic/FeatureSource/Actors/GeoIndexedStore.ts | 41 ++ .../Actors/MetaTagRecalculator.ts | 3 +- .../Actors/SaveFeatureSourceToLocalStorage.ts | 36 ++ .../Actors/SaveTileToLocalStorageActor.ts | 149 ----- .../FeatureSource/Actors/TileLocalStorage.ts | 63 ++ Logic/FeatureSource/FeaturePipeline.ts | 581 ------------------ Logic/FeatureSource/FeatureSource.ts | 5 +- .../PerLayerFeatureSourceSplitter.ts | 64 +- .../Sources/ClippedFeatureSource.ts | 17 + .../Sources/FilteringFeatureSource.ts | 41 +- Logic/FeatureSource/Sources/GeoJsonSource.ts | 2 +- .../{ => Sources}/LayoutSource.ts | 51 +- .../OsmFeatureSource.ts | 3 +- .../Sources}/OverpassFeatureSource.ts | 27 +- .../Sources/RememberingSource.ts | 34 - .../Sources/TouchesBboxFeatureSource.ts | 29 + .../DynamicGeoJsonTileSource.ts | 4 +- .../TiledFeatureSource/DynamicTileSource.ts | 3 +- .../FullNodeDatabaseSource.ts | 11 +- .../LocalStorageFeatureSource.ts | 28 + .../TiledFeatureSource/README.md | 24 - .../TiledFeatureSource/TileHierarchy.ts | 24 - .../TiledFeatureSource/TileHierarchyMerger.ts | 58 -- .../TiledFeatureSource/TiledFeatureSource.ts | 249 -------- Logic/GeoOperations.ts | 118 ++-- .../CreateMultiPolygonWithPointReuseAction.ts | 14 +- .../Actions/CreateWayWithPointReuseAction.ts | 16 +- Logic/Osm/Actions/ReplaceGeometryAction.ts | 12 +- Logic/Osm/Geocoding.ts | 4 + Logic/SimpleMetaTagger.ts | 103 ++-- Logic/State/FeaturePipelineState.ts | 99 +-- Logic/State/LayerState.ts | 87 +-- Logic/State/UserRelatedState.ts | 6 +- Logic/UIEventSource.ts | 33 +- Logic/Web/MangroveReviews.ts | 29 +- Models/FilteredLayer.ts | 92 ++- Models/GlobalFilter.ts | 5 +- Models/MapProperties.ts | 4 +- Models/RasterLayers.ts | 9 + Models/ThemeConfig/FilterConfig.ts | 61 +- Models/ThemeConfig/Json/LayoutConfigJson.ts | 19 - Models/ThemeConfig/LayoutConfig.ts | 20 - Models/ThemeConfig/PointRenderingConfig.ts | 10 +- Models/ThemeViewState.ts | 278 +++++++++ State.ts | 16 - UI/Base/Checkbox.svelte | 13 + UI/Base/Dropdown.svelte | 15 + UI/Base/If.svelte | 11 +- UI/Base/IfNot.svelte | 18 + UI/Base/Loading.svelte | 13 + UI/Base/MapControlButton.svelte | 2 +- UI/Base/ToSvelte.svelte | 27 +- UI/BigComponents/ActionButtons.ts | 6 +- UI/BigComponents/CopyrightPanel.ts | 79 ++- UI/BigComponents/DownloadPanel.ts | 1 - UI/BigComponents/FilterView.ts | 113 +--- UI/BigComponents/Filterview.svelte | 79 +++ UI/BigComponents/GeolocationControl.ts | 10 +- UI/BigComponents/Geosearch.svelte | 94 +++ UI/BigComponents/RightControls.ts | 6 - UI/BigComponents/SelectedElementView.svelte | 75 +++ UI/BigComponents/StatisticsPanel.ts | 77 +-- UI/BigComponents/ThemeIntroductionPanel.ts | 2 + UI/BigComponents/UploadTraceToOsmUI.ts | 2 +- UI/DefaultGUI.ts | 36 -- UI/DefaultGuiState.ts | 40 +- UI/Image/DeleteImage.ts | 6 +- UI/Image/ImageCarousel.ts | 2 +- UI/Image/ImageUploadFlow.ts | 15 +- UI/Input/LengthInput.ts | 53 +- UI/Input/ValidatedTextField.ts | 15 +- UI/Map/MapLibreAdaptor.ts | 58 +- UI/Map/ShowDataLayer.ts | 51 +- UI/Map/ShowDataLayerOptions.ts | 11 +- UI/Map/ShowDataMultiLayer.ts | 39 -- UI/NewPoint/ConfirmLocationOfPoint.ts | 33 +- UI/OpeningHours/OpeningHours.ts | 2 +- UI/OpeningHours/OpeningHoursVisualization.ts | 4 +- UI/Popup/AddNoteCommentViz.ts | 27 +- UI/Popup/AutoApplyButton.ts | 64 +- UI/Popup/CloseNoteButton.ts | 12 +- UI/Popup/ExportAsGpxViz.ts | 13 +- UI/Popup/HistogramViz.ts | 17 +- UI/Popup/ImportButton.ts | 246 ++++---- UI/Popup/LanguageElement.ts | 16 +- UI/Popup/MapillaryLinkVis.ts | 14 +- UI/Popup/MinimapViz.ts | 79 ++- UI/Popup/MultiApply.ts | 18 +- UI/Popup/MultiApplyViz.ts | 10 +- UI/Popup/NearbyImageVis.ts | 19 +- UI/Popup/NearbyImages.ts | 20 +- UI/Popup/PlantNetDetectionViz.ts | 6 +- UI/Popup/ShareLinkViz.ts | 10 +- UI/Popup/StealViz.ts | 8 +- UI/Popup/TagApplyButton.ts | 20 +- UI/Popup/TagRenderingAnswer.svelte | 0 UI/Popup/TagRenderingAnswer.ts | 4 +- UI/Popup/UploadToOsmViz.ts | 14 +- UI/SpecialVisualization.ts | 68 +- UI/SpecialVisualizations.ts | 199 +++--- UI/SubstitutedTranslation.ts | 16 +- UI/ThemeViewGUI.svelte | 275 +++++---- Utils.ts | 30 + css/index-tailwind-output.css | 177 +++--- index.css | 19 - index.ts | 7 - package-lock.json | 159 +---- package.json | 2 + scripts/slice.ts | 145 +++-- test.ts | 8 +- 114 files changed, 2412 insertions(+), 2958 deletions(-) create mode 100644 Logic/FeatureSource/Actors/GeoIndexedStore.ts create mode 100644 Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts delete mode 100644 Logic/FeatureSource/Actors/SaveTileToLocalStorageActor.ts create mode 100644 Logic/FeatureSource/Actors/TileLocalStorage.ts delete mode 100644 Logic/FeatureSource/FeaturePipeline.ts create mode 100644 Logic/FeatureSource/Sources/ClippedFeatureSource.ts rename Logic/FeatureSource/{ => Sources}/LayoutSource.ts (70%) rename Logic/FeatureSource/{TiledFeatureSource => Sources}/OsmFeatureSource.ts (98%) rename Logic/{Actors => FeatureSource/Sources}/OverpassFeatureSource.ts (88%) delete mode 100644 Logic/FeatureSource/Sources/RememberingSource.ts create mode 100644 Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts create mode 100644 Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts delete mode 100644 Logic/FeatureSource/TiledFeatureSource/README.md delete mode 100644 Logic/FeatureSource/TiledFeatureSource/TileHierarchy.ts delete mode 100644 Logic/FeatureSource/TiledFeatureSource/TileHierarchyMerger.ts delete mode 100644 Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource.ts create mode 100644 Models/ThemeViewState.ts delete mode 100644 State.ts create mode 100644 UI/Base/Checkbox.svelte create mode 100644 UI/Base/Dropdown.svelte create mode 100644 UI/Base/IfNot.svelte create mode 100644 UI/Base/Loading.svelte create mode 100644 UI/BigComponents/Filterview.svelte create mode 100644 UI/BigComponents/Geosearch.svelte create mode 100644 UI/BigComponents/SelectedElementView.svelte delete mode 100644 UI/Map/ShowDataMultiLayer.ts create mode 100644 UI/Popup/TagRenderingAnswer.svelte diff --git a/Logic/Actors/PendingChangesUploader.ts b/Logic/Actors/PendingChangesUploader.ts index cc7ebd4364..e71d0bf820 100644 --- a/Logic/Actors/PendingChangesUploader.ts +++ b/Logic/Actors/PendingChangesUploader.ts @@ -2,11 +2,12 @@ import { Changes } from "../Osm/Changes" import Constants from "../../Models/Constants" import { UIEventSource } from "../UIEventSource" import { Utils } from "../../Utils" +import { Feature } from "geojson" export default class PendingChangesUploader { private lastChange: Date - constructor(changes: Changes, selectedFeature: UIEventSource) { + constructor(changes: Changes, selectedFeature: UIEventSource) { const self = this this.lastChange = new Date() changes.pendingChanges.addCallback(() => { diff --git a/Logic/Actors/TitleHandler.ts b/Logic/Actors/TitleHandler.ts index 442158374b..f2228fa456 100644 --- a/Logic/Actors/TitleHandler.ts +++ b/Logic/Actors/TitleHandler.ts @@ -2,12 +2,19 @@ import { Store, UIEventSource } from "../UIEventSource" import Locale from "../../UI/i18n/Locale" import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer" import Combine from "../../UI/Base/Combine" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import { ElementStorage } from "../ElementStorage" import { Utils } from "../../Utils" +import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import { Feature } from "geojson" +import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" export default class TitleHandler { - constructor(selectedElement: Store, layout: LayoutConfig, allElements: ElementStorage) { + constructor( + selectedElement: Store, + selectedLayer: Store, + allElements: FeaturePropertiesStore, + layout: LayoutConfig + ) { const currentTitle: Store = selectedElement.map( (selected) => { const defaultTitle = layout?.title?.txt ?? "MapComplete" @@ -17,13 +24,14 @@ export default class TitleHandler { } const tags = selected.properties - for (const layer of layout.layers) { + for (const layer of layout?.layers ?? []) { if (layer.title === undefined) { continue } if (layer.source.osmTags.matchesProperties(tags)) { const tagsSource = - allElements.getEventSourceById(tags.id) ?? new UIEventSource(tags) + allElements.getStore(tags.id) ?? + new UIEventSource>(tags) const title = new TagRenderingAnswer(tagsSource, layer.title, {}) return ( new Combine([defaultTitle, " | ", title]).ConstructElement() @@ -33,7 +41,7 @@ export default class TitleHandler { } return defaultTitle }, - [Locale.language] + [Locale.language, selectedLayer] ) currentTitle.addCallbackAndRunD((title) => { diff --git a/Logic/ContributorCount.ts b/Logic/ContributorCount.ts index ef827585d8..d3ee51df48 100644 --- a/Logic/ContributorCount.ts +++ b/Logic/ContributorCount.ts @@ -1,39 +1,31 @@ /// Given a feature source, calculates a list of OSM-contributors who mapped the latest versions import { Store, UIEventSource } from "./UIEventSource" -import FeaturePipeline from "./FeatureSource/FeaturePipeline" -import Loc from "../Models/Loc" import { BBox } from "./BBox" +import GeoIndexedStore from "./FeatureSource/Actors/GeoIndexedStore" export default class ContributorCount { public readonly Contributors: UIEventSource> = new UIEventSource< Map >(new Map()) - private readonly state: { - featurePipeline: FeaturePipeline - currentBounds: Store - locationControl: Store - } + private readonly perLayer: ReadonlyMap private lastUpdate: Date = undefined constructor(state: { - featurePipeline: FeaturePipeline - currentBounds: Store - locationControl: Store + bounds: Store + dataIsLoading: Store + perLayer: ReadonlyMap }) { - this.state = state + this.perLayer = state.perLayer const self = this - state.currentBounds.map((bbox) => { - self.update(bbox) - }) - state.featurePipeline.runningQuery.addCallbackAndRun((_) => - self.update(state.currentBounds.data) + state.bounds.mapD( + (bbox) => { + self.update(bbox) + }, + [state.dataIsLoading] ) } private update(bbox: BBox) { - if (bbox === undefined) { - return - } const now = new Date() if ( this.lastUpdate !== undefined && @@ -42,7 +34,9 @@ export default class ContributorCount { return } this.lastUpdate = now - const featuresList = this.state.featurePipeline.GetAllFeaturesWithin(bbox) + const featuresList = [].concat( + Array.from(this.perLayer.values()).map((fs) => fs.GetFeaturesWithin(bbox)) + ) const hist = new Map() for (const list of featuresList) { for (const feature of list) { diff --git a/Logic/ExtraFunctions.ts b/Logic/ExtraFunctions.ts index da0c9ca0d2..f0a7d795b0 100644 --- a/Logic/ExtraFunctions.ts +++ b/Logic/ExtraFunctions.ts @@ -1,6 +1,5 @@ import { GeoOperations } from "./GeoOperations" import Combine from "../UI/Base/Combine" -import RelationsTracker from "./Osm/RelationsTracker" import BaseUIElement from "../UI/BaseUIElement" import List from "../UI/Base/List" import Title from "../UI/Base/Title" diff --git a/Logic/FeatureSource/Actors/GeoIndexedStore.ts b/Logic/FeatureSource/Actors/GeoIndexedStore.ts new file mode 100644 index 0000000000..ec11a9c706 --- /dev/null +++ b/Logic/FeatureSource/Actors/GeoIndexedStore.ts @@ -0,0 +1,41 @@ +import FeatureSource, { FeatureSourceForLayer } from "../FeatureSource" +import { Feature } from "geojson" +import { BBox } from "../../BBox" +import { GeoOperations } from "../../GeoOperations" +import { Store } from "../../UIEventSource" +import FilteredLayer from "../../../Models/FilteredLayer" + +/** + * Allows the retrieval of all features in the requested BBox; useful for one-shot queries; + * + * Use a ClippedFeatureSource for a continuously updating featuresource + */ +export default class GeoIndexedStore implements FeatureSource { + public features: Store + + constructor(features: FeatureSource | Store) { + this.features = features["features"] ?? features + } + + /** + * Gets the current features within the given bbox. + * + * @param bbox + * @constructor + */ + public GetFeaturesWithin(bbox: BBox): Feature[] { + // TODO optimize + const bboxFeature = bbox.asGeoJson({}) + return this.features.data.filter( + (f) => GeoOperations.intersect(f, bboxFeature) !== undefined + ) + } +} + +export class GeoIndexedStoreForLayer extends GeoIndexedStore implements FeatureSourceForLayer { + readonly layer: FilteredLayer + constructor(features: FeatureSource | Store, layer: FilteredLayer) { + super(features) + this.layer = layer + } +} diff --git a/Logic/FeatureSource/Actors/MetaTagRecalculator.ts b/Logic/FeatureSource/Actors/MetaTagRecalculator.ts index aacf44b502..0cda95578d 100644 --- a/Logic/FeatureSource/Actors/MetaTagRecalculator.ts +++ b/Logic/FeatureSource/Actors/MetaTagRecalculator.ts @@ -1,11 +1,10 @@ import { FeatureSourceForLayer, Tiled } from "../FeatureSource" import MetaTagging from "../../MetaTagging" import { ExtraFuncParams } from "../../ExtraFunctions" -import FeaturePipeline from "../FeaturePipeline" import { BBox } from "../../BBox" import { UIEventSource } from "../../UIEventSource" -/**** +/** * Concerned with the logic of updating the right layer at the right time */ class MetatagUpdater { diff --git a/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts b/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts new file mode 100644 index 0000000000..29519b6bbf --- /dev/null +++ b/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts @@ -0,0 +1,36 @@ +import FeatureSource, { Tiled } from "../FeatureSource" +import { Tiles } from "../../../Models/TileRange" +import { IdbLocalStorage } from "../../Web/IdbLocalStorage" +import { UIEventSource } from "../../UIEventSource" +import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" +import { BBox } from "../../BBox" +import SimpleFeatureSource from "../Sources/SimpleFeatureSource" +import FilteredLayer from "../../../Models/FilteredLayer" +import Loc from "../../../Models/Loc" +import { Feature } from "geojson" +import TileLocalStorage from "./TileLocalStorage" +import { GeoOperations } from "../../GeoOperations" +import { Utils } from "../../../Utils" + +/*** + * Saves all the features that are passed in to localstorage, so they can be retrieved on the next run + * + * The data is saved in a tiled way on a fixed zoomlevel and is retrievable per layer. + * + * Also see the sibling class + */ +export default class SaveFeatureSourceToLocalStorage { + constructor(layername: string, zoomlevel: number, features: FeatureSource) { + const storage = TileLocalStorage.construct(layername) + features.features.addCallbackAndRunD((features) => { + const sliced = GeoOperations.slice(zoomlevel, features) + sliced.forEach((features, tileIndex) => { + const src = storage.getTileSource(tileIndex) + if (Utils.sameList(src.data, features)) { + return + } + src.setData(features) + }) + }) + } +} diff --git a/Logic/FeatureSource/Actors/SaveTileToLocalStorageActor.ts b/Logic/FeatureSource/Actors/SaveTileToLocalStorageActor.ts deleted file mode 100644 index 9cf1b1de51..0000000000 --- a/Logic/FeatureSource/Actors/SaveTileToLocalStorageActor.ts +++ /dev/null @@ -1,149 +0,0 @@ -import FeatureSource, { Tiled } from "../FeatureSource" -import { Tiles } from "../../../Models/TileRange" -import { IdbLocalStorage } from "../../Web/IdbLocalStorage" -import { UIEventSource } from "../../UIEventSource" -import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" -import { BBox } from "../../BBox" -import SimpleFeatureSource from "../Sources/SimpleFeatureSource" -import FilteredLayer from "../../../Models/FilteredLayer" -import Loc from "../../../Models/Loc" -import { Feature } from "geojson" - -/*** - * Saves all the features that are passed in to localstorage, so they can be retrieved on the next run - * - * Technically, more an Actor then a featuresource, but it fits more neatly this way - */ -export default class SaveTileToLocalStorageActor { - private readonly visitedTiles: UIEventSource> - private readonly _layer: LayerConfig - private readonly _flayer: FilteredLayer - private readonly initializeTime = new Date() - - constructor(layer: FilteredLayer) { - this._flayer = layer - this._layer = layer.layerDef - this.visitedTiles = IdbLocalStorage.Get("visited_tiles_" + this._layer.id, { - defaultValue: new Map(), - }) - this.visitedTiles.stabilized(100).addCallbackAndRunD((tiles) => { - for (const key of Array.from(tiles.keys())) { - const tileFreshness = tiles.get(key) - - const toOld = - this.initializeTime.getTime() - tileFreshness.getTime() > - 1000 * this._layer.maxAgeOfCache - if (toOld) { - // Purge this tile - this.SetIdb(key, undefined) - console.debug("Purging tile", this._layer.id, key) - tiles.delete(key) - } - } - this.visitedTiles.ping() - return true - }) - } - - public LoadTilesFromDisk( - currentBounds: UIEventSource, - location: UIEventSource, - registerFreshness: (tileId: number, freshness: Date) => void, - registerTile: (src: FeatureSource & Tiled) => void - ) { - const self = this - const loadedTiles = new Set() - this.visitedTiles.addCallbackD((tiles) => { - if (tiles.size === 0) { - // We don't do anything yet as probably not yet loaded from disk - // We'll unregister later on - return - } - currentBounds.addCallbackAndRunD((bbox) => { - if (self._layer.minzoomVisible > location.data.zoom) { - // Not enough zoom - return - } - - // Iterate over all available keys in the local storage, check which are needed and fresh enough - for (const key of Array.from(tiles.keys())) { - const tileFreshness = tiles.get(key) - if (tileFreshness > self.initializeTime) { - // This tile is loaded by another source - continue - } - - registerFreshness(key, tileFreshness) - const tileBbox = BBox.fromTileIndex(key) - if (!bbox.overlapsWith(tileBbox)) { - continue - } - if (loadedTiles.has(key)) { - // Already loaded earlier - continue - } - loadedTiles.add(key) - this.GetIdb(key).then((features: Feature[]) => { - if (features === undefined) { - return - } - console.debug("Loaded tile " + self._layer.id + "_" + key + " from disk") - const src = new SimpleFeatureSource( - self._flayer, - key, - new UIEventSource(features) - ) - registerTile(src) - }) - } - }) - - return true // Remove the callback - }) - } - - public addTile(tile: FeatureSource & Tiled) { - const self = this - tile.features.addCallbackAndRunD((features) => { - const now = new Date() - - if (features.length > 0) { - self.SetIdb(tile.tileIndex, features) - } - // We _still_ write the time to know that this tile is empty! - this.MarkVisited(tile.tileIndex, now) - }) - } - - public poison(lon: number, lat: number) { - for (let z = 0; z < 25; z++) { - const { x, y } = Tiles.embedded_tile(lat, lon, z) - const tileId = Tiles.tile_index(z, x, y) - this.visitedTiles.data.delete(tileId) - } - } - - public MarkVisited(tileId: number, freshness: Date) { - this.visitedTiles.data.set(tileId, freshness) - this.visitedTiles.ping() - } - - private SetIdb(tileIndex, data) { - try { - IdbLocalStorage.SetDirectly(this._layer.id + "_" + tileIndex, data) - } catch (e) { - console.error( - "Could not save tile to indexed-db: ", - e, - "tileIndex is:", - tileIndex, - "for layer", - this._layer.id - ) - } - } - - private GetIdb(tileIndex) { - return IdbLocalStorage.GetDirectly(this._layer.id + "_" + tileIndex) - } -} diff --git a/Logic/FeatureSource/Actors/TileLocalStorage.ts b/Logic/FeatureSource/Actors/TileLocalStorage.ts new file mode 100644 index 0000000000..642110d4ee --- /dev/null +++ b/Logic/FeatureSource/Actors/TileLocalStorage.ts @@ -0,0 +1,63 @@ +import { IdbLocalStorage } from "../../Web/IdbLocalStorage" +import { UIEventSource } from "../../UIEventSource" + +/** + * A class which allows to read/write a tile to local storage. + * + * Does the heavy lifting for LocalStorageFeatureSource and SaveFeatureToLocalStorage + */ +export default class TileLocalStorage { + private static perLayer: Record> = {} + private readonly _layername: string + private readonly cachedSources: Record> = {} + + private constructor(layername: string) { + this._layername = layername + } + + public static construct(layername: string): TileLocalStorage { + const cached = TileLocalStorage.perLayer[layername] + if (cached) { + return cached + } + + const tls = new TileLocalStorage(layername) + TileLocalStorage.perLayer[layername] = tls + return tls + } + + /** + * Constructs a UIEventSource element which is synced with localStorage + * @param layername + * @param tileIndex + */ + public getTileSource(tileIndex: number): UIEventSource { + const cached = this.cachedSources[tileIndex] + if (cached) { + return cached + } + const src = UIEventSource.FromPromise(this.GetIdb(tileIndex)) + src.addCallbackD((data) => this.SetIdb(tileIndex, data)) + this.cachedSources[tileIndex] = src + return src + } + + private SetIdb(tileIndex: number, data): void { + try { + IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data) + } catch (e) { + console.error( + "Could not save tile to indexed-db: ", + e, + "tileIndex is:", + tileIndex, + "for layer", + this._layername + ) + } + } + + private GetIdb(tileIndex: number): Promise { + return IdbLocalStorage.GetDirectly(this._layername + "_" + tileIndex) + } +} diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts deleted file mode 100644 index fc7e668d79..0000000000 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ /dev/null @@ -1,581 +0,0 @@ -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import FilteringFeatureSource from "./Sources/FilteringFeatureSource" -import PerLayerFeatureSourceSplitter from "./PerLayerFeatureSourceSplitter" -import FeatureSource, { FeatureSourceForLayer, IndexedFeatureSource, Tiled } from "./FeatureSource" -import TiledFeatureSource from "./TiledFeatureSource/TiledFeatureSource" -import { Store, UIEventSource } from "../UIEventSource" -import { TileHierarchyTools } from "./TiledFeatureSource/TileHierarchy" -import RememberingSource from "./Sources/RememberingSource" -import OverpassFeatureSource from "../Actors/OverpassFeatureSource" -import GeoJsonSource from "./Sources/GeoJsonSource" -import Loc from "../../Models/Loc" -import RegisteringAllFromFeatureSourceActor from "./Actors/RegisteringAllFromFeatureSourceActor" -import SaveTileToLocalStorageActor from "./Actors/SaveTileToLocalStorageActor" -import DynamicGeoJsonTileSource from "./TiledFeatureSource/DynamicGeoJsonTileSource" -import { TileHierarchyMerger } from "./TiledFeatureSource/TileHierarchyMerger" -import { NewGeometryFromChangesFeatureSource } from "./Sources/NewGeometryFromChangesFeatureSource" -import ChangeGeometryApplicator from "./Sources/ChangeGeometryApplicator" -/** - * Keeps track of the age of the loaded data. - * Has one freshness-Calculator for every layer - * @private - */ -import { BBox } from "../BBox" -import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource" -import { Tiles } from "../../Models/TileRange" -import FullNodeDatabaseSource from "./TiledFeatureSource/FullNodeDatabaseSource" -import MapState from "../State/MapState" -import { OsmFeature } from "../../Models/OsmFeature" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { FilterState } from "../../Models/FilteredLayer" -import { GeoOperations } from "../GeoOperations" -import { Utils } from "../../Utils" - -/** - * The features pipeline ties together a myriad of various datasources: - * - * - The Overpass-API - * - The OSM-API - * - Third-party geojson files, either sliced or directly. - * - * In order to truly understand this class, please have a look at the following diagram: https://cdn-images-1.medium.com/fit/c/800/618/1*qTK1iCtyJUr4zOyw4IFD7A.jpeg - * - * - */ -export default class FeaturePipeline { - public readonly sufficientlyZoomed: Store - public readonly runningQuery: Store - public readonly timeout: UIEventSource - public readonly somethingLoaded: UIEventSource = new UIEventSource(false) - public readonly newDataLoadedSignal: UIEventSource = - new UIEventSource(undefined) - /** - * Keeps track of all raw OSM-nodes. - * Only initialized if `ReplaceGeometryAction` is needed somewhere - */ - public readonly fullNodeDatabase?: FullNodeDatabaseSource - private readonly overpassUpdater: OverpassFeatureSource - private state: MapState - private readonly perLayerHierarchy: Map - private readonly oldestAllowedDate: Date - private readonly osmSourceZoomLevel - private readonly localStorageSavers = new Map() - - private readonly newGeometryHandler: NewGeometryFromChangesFeatureSource - - constructor( - handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void, - state: MapState, - options?: { - /*Used for metatagging - will receive all the sources with changeGeometry applied but without filtering*/ - handleRawFeatureSource: (source: FeatureSourceForLayer) => void - } - ) { - this.state = state - - const self = this - const expiryInSeconds = Math.min( - ...(state.layoutToUse?.layers?.map((l) => l.maxAgeOfCache) ?? []) - ) - this.oldestAllowedDate = new Date(new Date().getTime() - expiryInSeconds) - this.osmSourceZoomLevel = state.osmApiTileSize.data - const useOsmApi = state.locationControl.map( - (l) => l.zoom > (state.overpassMaxZoom.data ?? 12) - ) - - state.changes.allChanges.addCallbackAndRun((allChanges) => { - allChanges - .filter((ch) => ch.id < 0 && ch.changes !== undefined) - .map((ch) => ch.changes) - .filter((coor) => coor["lat"] !== undefined && coor["lon"] !== undefined) - .forEach((coor) => { - state.layoutToUse.layers.forEach((l) => - self.localStorageSavers.get(l.id)?.poison(coor["lon"], coor["lat"]) - ) - }) - }) - - this.sufficientlyZoomed = state.locationControl.map((location) => { - if (location?.zoom === undefined) { - return false - } - let minzoom = Math.min( - ...state.filteredLayers.data.map((layer) => layer.layerDef.minzoom ?? 18) - ) - return location.zoom >= minzoom - }) - - const neededTilesFromOsm = this.getNeededTilesFromOsm(this.sufficientlyZoomed) - - const perLayerHierarchy = new Map() - this.perLayerHierarchy = perLayerHierarchy - - // Given a tile, wraps it and passes it on to render (handled by 'handleFeatureSource' - function patchedHandleFeatureSource( - src: FeatureSourceForLayer & IndexedFeatureSource & Tiled - ) { - // This will already contain the merged features for this tile. In other words, this will only be triggered once for every tile - const withChanges = new ChangeGeometryApplicator(src, state.changes) - const srcFiltered = new FilteringFeatureSource(state, src.tileIndex, withChanges) - - handleFeatureSource(srcFiltered) - if (options?.handleRawFeatureSource) { - options.handleRawFeatureSource(withChanges) - } - self.somethingLoaded.setData(true) - // We do not mark as visited here, this is the responsability of the code near the actual loader (e.g. overpassLoader and OSMApiFeatureLoader) - } - - for (const filteredLayer of state.filteredLayers.data) { - const id = filteredLayer.layerDef.id - const source = filteredLayer.layerDef.source - - const hierarchy = new TileHierarchyMerger(filteredLayer, (tile, _) => - patchedHandleFeatureSource(tile) - ) - perLayerHierarchy.set(id, hierarchy) - - if (id === "type_node") { - this.fullNodeDatabase = new FullNodeDatabaseSource(filteredLayer, (tile) => { - perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile) - tile.features.addCallbackAndRunD((_) => self.onNewDataLoaded(tile)) - }) - continue - } - - const localTileSaver = new SaveTileToLocalStorageActor(filteredLayer) - this.localStorageSavers.set(filteredLayer.layerDef.id, localTileSaver) - - if (source.geojsonSource === undefined) { - // This is an OSM layer - // We load the cached values and register them - // Getting data from upstream happens a bit lower - localTileSaver.LoadTilesFromDisk( - state.currentBounds, - state.locationControl, - (tileIndex, freshness) => - self.freshnesses.get(id).addTileLoad(tileIndex, freshness), - (tile) => { - console.debug("Loaded tile ", id, tile.tileIndex, "from local cache") - new RegisteringAllFromFeatureSourceActor(tile, state.allElements) - hierarchy.registerTile(tile) - tile.features.addCallbackAndRunD((_) => self.onNewDataLoaded(tile)) - } - ) - - continue - } - - if (source.geojsonZoomLevel === undefined) { - // This is a 'load everything at once' geojson layer - const src = new GeoJsonSource(filteredLayer) - - if (source.isOsmCacheLayer) { - // We split them up into tiles anyway as it is an OSM source - TiledFeatureSource.createHierarchy(src, { - layer: src.layer, - minZoomLevel: this.osmSourceZoomLevel, - noDuplicates: true, - registerTile: (tile) => { - new RegisteringAllFromFeatureSourceActor(tile, state.allElements) - perLayerHierarchy.get(id).registerTile(tile) - tile.features.addCallbackAndRunD((_) => self.onNewDataLoaded(tile)) - }, - }) - } else { - new RegisteringAllFromFeatureSourceActor(src, state.allElements) - perLayerHierarchy.get(id).registerTile(src) - src.features.addCallbackAndRunD((_) => self.onNewDataLoaded(src)) - } - } else { - new DynamicGeoJsonTileSource( - filteredLayer, - (tile) => { - new RegisteringAllFromFeatureSourceActor(tile, state.allElements) - perLayerHierarchy.get(id).registerTile(tile) - tile.features.addCallbackAndRunD((_) => self.onNewDataLoaded(tile)) - }, - state - ) - } - } - - const osmFeatureSource = new OsmFeatureSource({ - isActive: useOsmApi, - neededTiles: neededTilesFromOsm, - handleTile: (tile) => { - new RegisteringAllFromFeatureSourceActor(tile, state.allElements) - if (tile.layer.layerDef.maxAgeOfCache > 0) { - const saver = self.localStorageSavers.get(tile.layer.layerDef.id) - if (saver === undefined) { - console.error( - "No localStorageSaver found for layer ", - tile.layer.layerDef.id - ) - } - saver?.addTile(tile) - } - perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile) - tile.features.addCallbackAndRunD((_) => self.onNewDataLoaded(tile)) - }, - state: state, - markTileVisited: (tileId) => - state.filteredLayers.data.forEach((flayer) => { - const layer = flayer.layerDef - if (layer.maxAgeOfCache > 0) { - const saver = self.localStorageSavers.get(layer.id) - if (saver === undefined) { - console.error("No local storage saver found for ", layer.id) - } else { - saver.MarkVisited(tileId, new Date()) - } - } - self.freshnesses.get(layer.id).addTileLoad(tileId, new Date()) - }), - }) - - if (this.fullNodeDatabase !== undefined) { - osmFeatureSource.rawDataHandlers.push((osmJson, tileId) => - this.fullNodeDatabase.handleOsmJson(osmJson, tileId) - ) - } - - const updater = this.initOverpassUpdater(state, useOsmApi) - this.overpassUpdater = updater - this.timeout = updater.timeout - - // Actually load data from the overpass source - new PerLayerFeatureSourceSplitter( - state.filteredLayers, - (source) => - TiledFeatureSource.createHierarchy(source, { - layer: source.layer, - minZoomLevel: source.layer.layerDef.minzoom, - noDuplicates: true, - maxFeatureCount: state.layoutToUse.clustering.minNeededElements, - maxZoomLevel: state.layoutToUse.clustering.maxZoom, - registerTile: (tile) => { - // We save the tile data for the given layer to local storage - data sourced from overpass - self.localStorageSavers.get(tile.layer.layerDef.id)?.addTile(tile) - perLayerHierarchy - .get(source.layer.layerDef.id) - .registerTile(new RememberingSource(tile)) - tile.features.addCallbackAndRunD((f) => { - if (f.length === 0) { - return - } - self.onNewDataLoaded(tile) - }) - }, - }), - updater, - { - handleLeftovers: (leftOvers) => { - console.warn("Overpass returned a few non-matched features:", leftOvers) - }, - } - ) - - // Also load points/lines that are newly added. - const newGeometry = new NewGeometryFromChangesFeatureSource( - state.changes, - state.allElements, - state.osmConnection._oauth_config.url - ) - this.newGeometryHandler = newGeometry - newGeometry.features.addCallbackAndRun((geometries) => { - console.debug("New geometries are:", geometries) - }) - - new RegisteringAllFromFeatureSourceActor(newGeometry, state.allElements) - // A NewGeometryFromChangesFeatureSource does not split per layer, so we do this next - new PerLayerFeatureSourceSplitter( - state.filteredLayers, - (perLayer) => { - // We don't bother to split them over tiles as it'll contain little features by default, so we simply add them like this - perLayerHierarchy.get(perLayer.layer.layerDef.id).registerTile(perLayer) - // AT last, we always apply the metatags whenever possible - perLayer.features.addCallbackAndRunD((_) => { - self.onNewDataLoaded(perLayer) - }) - }, - newGeometry, - { - handleLeftovers: (leftOvers) => { - console.warn("Got some leftovers from the filteredLayers: ", leftOvers) - }, - } - ) - - this.runningQuery = updater.runningQuery.map( - (overpass) => { - console.log( - "FeaturePipeline: runningQuery state changed: Overpass", - overpass ? "is querying," : "is idle,", - "osmFeatureSource is", - osmFeatureSource.isRunning - ? "is running and needs " + - neededTilesFromOsm.data?.length + - " tiles (already got " + - osmFeatureSource.downloadedTiles.size + - " tiles )" - : "is idle" - ) - return overpass || osmFeatureSource.isRunning.data - }, - [osmFeatureSource.isRunning] - ) - } - - public GetAllFeaturesWithin(bbox: BBox): OsmFeature[][] { - const self = this - const tiles: OsmFeature[][] = [] - Array.from(this.perLayerHierarchy.keys()).forEach((key) => { - const fetched: OsmFeature[][] = self.GetFeaturesWithin(key, bbox) - tiles.push(...fetched) - }) - return tiles - } - - public GetAllFeaturesAndMetaWithin( - bbox: BBox, - layerIdWhitelist?: Set - ): { features: OsmFeature[]; layer: string }[] { - const self = this - const tiles: { features: any[]; layer: string }[] = [] - Array.from(this.perLayerHierarchy.keys()).forEach((key) => { - if (layerIdWhitelist !== undefined && !layerIdWhitelist.has(key)) { - return - } - return tiles.push({ - layer: key, - features: [].concat(...self.GetFeaturesWithin(key, bbox)), - }) - }) - return tiles - } - - /** - * Gets all the tiles which overlap with the given BBOX. - * This might imply that extra features might be shown - */ - public GetFeaturesWithin(layerId: string, bbox: BBox): OsmFeature[][] { - if (layerId === "*") { - return this.GetAllFeaturesWithin(bbox) - } - const requestedHierarchy = this.perLayerHierarchy.get(layerId) - if (requestedHierarchy === undefined) { - console.warn( - "Layer ", - layerId, - "is not defined. Try one of ", - Array.from(this.perLayerHierarchy.keys()) - ) - return undefined - } - return TileHierarchyTools.getTiles(requestedHierarchy, bbox) - .filter((featureSource) => featureSource.features?.data !== undefined) - .map((featureSource) => featureSource.features.data) - } - - public GetTilesPerLayerWithin( - bbox: BBox, - handleTile: (tile: FeatureSourceForLayer & Tiled) => void - ) { - Array.from(this.perLayerHierarchy.values()).forEach((hierarchy) => { - TileHierarchyTools.getTiles(hierarchy, bbox).forEach(handleTile) - }) - } - - private onNewDataLoaded(src: FeatureSource) { - this.newDataLoadedSignal.setData(src) - } - - private freshnessForVisibleLayers(z: number, x: number, y: number): Date { - let oldestDate = undefined - for (const flayer of this.state.filteredLayers.data) { - if (!flayer.isDisplayed.data && !flayer.layerDef.forceLoad) { - continue - } - if (this.state.locationControl.data.zoom < flayer.layerDef.minzoom) { - continue - } - if (flayer.layerDef.maxAgeOfCache === 0) { - return undefined - } - const freshnessCalc = this.freshnesses.get(flayer.layerDef.id) - if (freshnessCalc === undefined) { - console.warn("No freshness tracker found for ", flayer.layerDef.id) - return undefined - } - const freshness = freshnessCalc.freshnessFor(z, x, y) - if (freshness === undefined) { - // SOmething is undefined --> we return undefined as we have to download - return undefined - } - if (oldestDate === undefined || oldestDate > freshness) { - oldestDate = freshness - } - } - return oldestDate - } - - /* - * Gives an UIEventSource containing the tileIndexes of the tiles that should be loaded from OSM - * */ - private getNeededTilesFromOsm(isSufficientlyZoomed: Store): Store { - const self = this - return this.state.currentBounds.map( - (bbox) => { - if (bbox === undefined) { - return [] - } - if (!isSufficientlyZoomed.data) { - return [] - } - const osmSourceZoomLevel = self.osmSourceZoomLevel - const range = bbox.containingTileRange(osmSourceZoomLevel) - const tileIndexes = [] - if (range.total >= 100) { - // Too much tiles! - return undefined - } - Tiles.MapRange(range, (x, y) => { - const i = Tiles.tile_index(osmSourceZoomLevel, x, y) - const oldestDate = self.freshnessForVisibleLayers(osmSourceZoomLevel, x, y) - if (oldestDate !== undefined && oldestDate > this.oldestAllowedDate) { - console.debug( - "Skipping tile", - osmSourceZoomLevel, - x, - y, - "as a decently fresh one is available" - ) - // The cached tiles contain decently fresh data - return undefined - } - tileIndexes.push(i) - }) - return tileIndexes - }, - [isSufficientlyZoomed] - ) - } - - private initOverpassUpdater( - state: { - layoutToUse: LayoutConfig - currentBounds: Store - locationControl: Store - readonly overpassUrl: Store - readonly overpassTimeout: Store - readonly overpassMaxZoom: Store - }, - useOsmApi: Store - ): OverpassFeatureSource { - const minzoom = Math.min(...state.layoutToUse.layers.map((layer) => layer.minzoom)) - const overpassIsActive = state.currentBounds.map( - (bbox) => { - if (bbox === undefined) { - console.debug("Disabling overpass source: no bbox") - return false - } - let zoom = state.locationControl.data.zoom - if (zoom < minzoom) { - // We are zoomed out over the zoomlevel of any layer - console.debug("Disabling overpass source: zoom < minzoom") - return false - } - - const range = bbox.containingTileRange(zoom) - if (range.total >= 5000) { - // Let's assume we don't have so much data cached - return true - } - const self = this - const allFreshnesses = Tiles.MapRange(range, (x, y) => - self.freshnessForVisibleLayers(zoom, x, y) - ) - return allFreshnesses.some( - (freshness) => freshness === undefined || freshness < this.oldestAllowedDate - ) - }, - [state.locationControl] - ) - - return new OverpassFeatureSource(state, { - padToTiles: state.locationControl.map((l) => Math.min(15, l.zoom + 1)), - isActive: useOsmApi.map((b) => !b && overpassIsActive.data, [overpassIsActive]), - }) - } - - /** - * Builds upon 'GetAllFeaturesAndMetaWithin', but does stricter BBOX-checking and applies the filters - */ - public getAllVisibleElementsWithmeta( - bbox: BBox - ): { center: [number, number]; element: OsmFeature; layer: LayerConfig }[] { - if (bbox === undefined) { - console.warn("No bbox") - return [] - } - - const layers = Utils.toIdRecord(this.state.layoutToUse.layers) - const elementsWithMeta: { features: OsmFeature[]; layer: string }[] = - this.GetAllFeaturesAndMetaWithin(bbox) - - let elements: { center: [number, number]; element: OsmFeature; layer: LayerConfig }[] = [] - let seenElements = new Set() - for (const elementsWithMetaElement of elementsWithMeta) { - const layer = layers[elementsWithMetaElement.layer] - if (layer.title === undefined) { - continue - } - const filtered = this.state.filteredLayers.data.find((fl) => fl.layerDef == layer) - for (let i = 0; i < elementsWithMetaElement.features.length; i++) { - const element = elementsWithMetaElement.features[i] - if (!filtered.isDisplayed.data) { - continue - } - if (seenElements.has(element.properties.id)) { - continue - } - seenElements.add(element.properties.id) - if (!bbox.overlapsWith(BBox.get(element))) { - continue - } - if (layer?.isShown !== undefined && !layer.isShown.matchesProperties(element)) { - continue - } - const activeFilters: FilterState[] = Array.from( - filtered.appliedFilters.data.values() - ) - if ( - !activeFilters.every( - (filter) => - filter?.currentFilter === undefined || - filter?.currentFilter?.matchesProperties(element.properties) - ) - ) { - continue - } - const center = GeoOperations.centerpointCoordinates(element) - elements.push({ - element, - center, - layer: layers[elementsWithMetaElement.layer], - }) - } - } - - return elements - } - - /** - * Inject a new point - */ - InjectNewPoint(geojson) { - this.newGeometryHandler.features.data.push(geojson) - this.newGeometryHandler.features.ping() - } -} diff --git a/Logic/FeatureSource/FeatureSource.ts b/Logic/FeatureSource/FeatureSource.ts index 038132f48a..f55cd95920 100644 --- a/Logic/FeatureSource/FeatureSource.ts +++ b/Logic/FeatureSource/FeatureSource.ts @@ -1,4 +1,4 @@ -import { Store } from "../UIEventSource" +import { Store, UIEventSource } from "../UIEventSource" import FilteredLayer from "../../Models/FilteredLayer" import { BBox } from "../BBox" import { Feature } from "geojson" @@ -6,6 +6,9 @@ import { Feature } from "geojson" export default interface FeatureSource { features: Store } +export interface WritableFeatureSource extends FeatureSource { + features: UIEventSource +} export interface Tiled { tileIndex: number diff --git a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts index a2d17b5380..05a5d8f32e 100644 --- a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts +++ b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts @@ -1,48 +1,59 @@ -import FeatureSource from "./FeatureSource" -import { Store } from "../UIEventSource" +import FeatureSource, { FeatureSourceForLayer } from "./FeatureSource" import FilteredLayer from "../../Models/FilteredLayer" import SimpleFeatureSource from "./Sources/SimpleFeatureSource" import { Feature } from "geojson" +import { Utils } from "../../Utils" +import { UIEventSource } from "../UIEventSource" /** * In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled) * If this is the case, multiple objects with a different _matching_layer_id are generated. * In any case, this featureSource marks the objects with _matching_layer_id */ -export default class PerLayerFeatureSourceSplitter { +export default class PerLayerFeatureSourceSplitter< + T extends FeatureSourceForLayer = SimpleFeatureSource +> { + public readonly perLayer: ReadonlyMap constructor( - layers: Store, - handleLayerData: (source: FeatureSource, layer: FilteredLayer) => void, + layers: FilteredLayer[], upstream: FeatureSource, options?: { - tileIndex?: number + constructStore?: (features: UIEventSource, layer: FilteredLayer) => T handleLeftovers?: (featuresWithoutLayer: any[]) => void } ) { - const knownLayers = new Map() + const knownLayers = new Map() + this.perLayer = knownLayers + const layerSources = new Map>() - function update() { - const features = upstream.features?.data + const constructStore = + options?.constructStore ?? ((store, layer) => new SimpleFeatureSource(layer, store)) + for (const layer of layers) { + const src = new UIEventSource([]) + layerSources.set(layer.layerDef.id, src) + knownLayers.set(layer.layerDef.id, constructStore(src, layer)) + } + + upstream.features.addCallbackAndRunD((features) => { if (features === undefined) { return } - if (layers.data === undefined || layers.data.length === 0) { + if (layers === undefined) { return } // We try to figure out (for each feature) in which feature store it should be saved. - // Note that this splitter is only run when it is invoked by the overpass feature source, so we can't be sure in which layer it should go const featuresPerLayer = new Map() - const noLayerFound = [] + const noLayerFound: Feature[] = [] - for (const layer of layers.data) { + for (const layer of layers) { featuresPerLayer.set(layer.layerDef.id, []) } for (const f of features) { let foundALayer = false - for (const layer of layers.data) { + for (const layer of layers) { if (layer.layerDef.source.osmTags.matchesProperties(f.properties)) { // We have found our matching layer! featuresPerLayer.get(layer.layerDef.id).push(f) @@ -60,7 +71,7 @@ export default class PerLayerFeatureSourceSplitter { // At this point, we have our features per layer as a list // We assign them to the correct featureSources - for (const layer of layers.data) { + for (const layer of layers) { const id = layer.layerDef.id const features = featuresPerLayer.get(id) if (features === undefined) { @@ -68,25 +79,24 @@ export default class PerLayerFeatureSourceSplitter { continue } - let featureSource = knownLayers.get(id) - if (featureSource === undefined) { - // Not yet initialized - now is a good time - featureSource = new SimpleFeatureSource(layer) - featureSource.features.setData(features) - knownLayers.set(id, featureSource) - handleLayerData(featureSource, layer) - } else { - featureSource.features.setData(features) + const src = layerSources.get(id) + + if (Utils.sameList(src.data, features)) { + return } + src.setData(features) } // AT last, the leftovers are handled if (options?.handleLeftovers !== undefined && noLayerFound.length > 0) { options.handleLeftovers(noLayerFound) } - } + }) + } - layers.addCallback((_) => update()) - upstream.features.addCallbackAndRunD((_) => update()) + public forEach(f: (featureSource: FeatureSourceForLayer) => void) { + for (const fs of this.perLayer.values()) { + f(fs) + } } } diff --git a/Logic/FeatureSource/Sources/ClippedFeatureSource.ts b/Logic/FeatureSource/Sources/ClippedFeatureSource.ts new file mode 100644 index 0000000000..f382a594cf --- /dev/null +++ b/Logic/FeatureSource/Sources/ClippedFeatureSource.ts @@ -0,0 +1,17 @@ +import FeatureSource from "../FeatureSource" +import { Feature, Polygon } from "geojson" +import StaticFeatureSource from "./StaticFeatureSource" +import { GeoOperations } from "../../GeoOperations" + +/** + * Returns a clipped version of the original geojson. Ways which partially intersect the given feature will be split up + */ +export default class ClippedFeatureSource extends StaticFeatureSource { + constructor(features: FeatureSource, clipTo: Feature) { + super( + features.features.mapD((features) => { + return [].concat(features.map((feature) => GeoOperations.clipWith(feature, clipTo))) + }) + ) + } +} diff --git a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts index dcbbef71ef..383f70c624 100644 --- a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts @@ -1,15 +1,15 @@ import { Store, UIEventSource } from "../../UIEventSource" -import FilteredLayer, { FilterState } from "../../../Models/FilteredLayer" +import FilteredLayer from "../../../Models/FilteredLayer" import FeatureSource from "../FeatureSource" import { TagsFilter } from "../../Tags/TagsFilter" import { Feature } from "geojson" -import { OsmTags } from "../../../Models/OsmFeature" +import { GlobalFilter } from "../../../Models/GlobalFilter" export default class FilteringFeatureSource implements FeatureSource { public features: UIEventSource = new UIEventSource([]) private readonly upstream: FeatureSource - private readonly _fetchStore?: (id: String) => Store - private readonly _globalFilters?: Store<{ filter: FilterState }[]> + private readonly _fetchStore?: (id: string) => Store> + private readonly _globalFilters?: Store private readonly _alreadyRegistered = new Set>() private readonly _is_dirty = new UIEventSource(false) private readonly _layer: FilteredLayer @@ -18,8 +18,8 @@ export default class FilteringFeatureSource implements FeatureSource { constructor( layer: FilteredLayer, upstream: FeatureSource, - fetchStore?: (id: String) => Store, - globalFilters?: Store<{ filter: FilterState }[]>, + fetchStore?: (id: string) => Store>, + globalFilters?: Store, metataggingUpdated?: Store ) { this.upstream = upstream @@ -32,9 +32,11 @@ export default class FilteringFeatureSource implements FeatureSource { self.update() }) - layer.appliedFilters.addCallback((_) => { - self.update() - }) + layer.appliedFilters.forEach((value) => + value.addCallback((_) => { + self.update() + }) + ) this._is_dirty.stabilized(1000).addCallbackAndRunD((dirty) => { if (dirty) { @@ -58,7 +60,7 @@ export default class FilteringFeatureSource implements FeatureSource { const layer = this._layer const features: Feature[] = this.upstream.features.data ?? [] const includedFeatureIds = new Set() - const globalFilters = self._globalFilters?.data?.map((f) => f.filter) + const globalFilters = self._globalFilters?.data?.map((f) => f) const newFeatures = (features ?? []).filter((f) => { self.registerCallback(f) @@ -71,19 +73,26 @@ export default class FilteringFeatureSource implements FeatureSource { return false } - const tagsFilter = Array.from(layer.appliedFilters?.data?.values() ?? []) - for (const filter of tagsFilter) { - const neededTags: TagsFilter = filter?.currentFilter + for (const filter of layer.layerDef.filters) { + const state = layer.appliedFilters.get(filter.id).data + if (state === undefined) { + continue + } + let neededTags: TagsFilter + if (typeof state === "string") { + // This filter uses fields + } else { + neededTags = filter.options[state].osmTags + } if (neededTags !== undefined && !neededTags.matchesProperties(f.properties)) { // Hidden by the filter on the layer itself - we want to hide it no matter what return false } } - for (const filter of globalFilters ?? []) { - const neededTags: TagsFilter = filter?.currentFilter + for (const globalFilter of globalFilters ?? []) { + const neededTags = globalFilter.osmTags if (neededTags !== undefined && !neededTags.matchesProperties(f.properties)) { - // Hidden by the filter on the layer itself - we want to hide it no matter what return false } } diff --git a/Logic/FeatureSource/Sources/GeoJsonSource.ts b/Logic/FeatureSource/Sources/GeoJsonSource.ts index 30e4fa6048..82897cf227 100644 --- a/Logic/FeatureSource/Sources/GeoJsonSource.ts +++ b/Logic/FeatureSource/Sources/GeoJsonSource.ts @@ -58,7 +58,7 @@ export default class GeoJsonSource implements FeatureSource { .replace("{x_max}", "" + bounds.maxLon) } - const eventsource = new UIEventSource(undefined) + const eventsource = new UIEventSource([]) if (options?.isActive !== undefined) { options.isActive.addCallbackAndRunD(async (active) => { if (!active) { diff --git a/Logic/FeatureSource/LayoutSource.ts b/Logic/FeatureSource/Sources/LayoutSource.ts similarity index 70% rename from Logic/FeatureSource/LayoutSource.ts rename to Logic/FeatureSource/Sources/LayoutSource.ts index b4d62b49a2..7e7f43fb64 100644 --- a/Logic/FeatureSource/LayoutSource.ts +++ b/Logic/FeatureSource/Sources/LayoutSource.ts @@ -1,14 +1,15 @@ -import FeatureSource from "./FeatureSource" -import { Store } from "../UIEventSource" -import FeatureSwitchState from "../State/FeatureSwitchState" -import OverpassFeatureSource from "../Actors/OverpassFeatureSource" -import { BBox } from "../BBox" -import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource" -import { Or } from "../Tags/Or" -import FeatureSourceMerger from "./Sources/FeatureSourceMerger" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import GeoJsonSource from "./Sources/GeoJsonSource" -import DynamicGeoJsonTileSource from "./TiledFeatureSource/DynamicGeoJsonTileSource" +import GeoJsonSource from "./GeoJsonSource" +import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" +import FeatureSource from "../FeatureSource" +import { Or } from "../../Tags/Or" +import FeatureSwitchState from "../../State/FeatureSwitchState" +import OverpassFeatureSource from "./OverpassFeatureSource" +import { Store } from "../../UIEventSource" +import OsmFeatureSource from "./OsmFeatureSource" +import FeatureSourceMerger from "./FeatureSourceMerger" +import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource" +import { BBox } from "../../BBox" +import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeatureSource" /** * This source will fetch the needed data from various sources for the given layout. @@ -17,22 +18,24 @@ import DynamicGeoJsonTileSource from "./TiledFeatureSource/DynamicGeoJsonTileSou */ export default class LayoutSource extends FeatureSourceMerger { constructor( - filteredLayers: LayerConfig[], + layers: LayerConfig[], featureSwitches: FeatureSwitchState, newAndChangedElements: FeatureSource, mapProperties: { bounds: Store; zoom: Store }, backend: string, - isLayerActive: (id: string) => Store + isDisplayed: (id: string) => Store ) { const { bounds, zoom } = mapProperties // remove all 'special' layers - filteredLayers = filteredLayers.filter((flayer) => flayer.source !== null) + layers = layers.filter((flayer) => flayer.source !== null) - const geojsonlayers = filteredLayers.filter( - (flayer) => flayer.source.geojsonSource !== undefined - ) - const osmLayers = filteredLayers.filter( - (flayer) => flayer.source.geojsonSource === undefined + const geojsonlayers = layers.filter((layer) => layer.source.geojsonSource !== undefined) + const osmLayers = layers.filter((layer) => layer.source.geojsonSource === undefined) + const fromCache = osmLayers.map( + (l) => + new LocalStorageFeatureSource(l.id, 15, mapProperties, { + isActive: isDisplayed(l.id), + }) ) const overpassSource = LayoutSource.setupOverpass(osmLayers, bounds, zoom, featureSwitches) const osmApiSource = LayoutSource.setupOsmApiSource( @@ -43,11 +46,11 @@ export default class LayoutSource extends FeatureSourceMerger { featureSwitches ) const geojsonSources: FeatureSource[] = geojsonlayers.map((l) => - LayoutSource.setupGeojsonSource(l, mapProperties) + LayoutSource.setupGeojsonSource(l, mapProperties, isDisplayed(l.id)) ) - const expiryInSeconds = Math.min(...(filteredLayers?.map((l) => l.maxAgeOfCache) ?? [])) - super(overpassSource, osmApiSource, newAndChangedElements, ...geojsonSources) + const expiryInSeconds = Math.min(...(layers?.map((l) => l.maxAgeOfCache) ?? [])) + super(overpassSource, osmApiSource, newAndChangedElements, ...geojsonSources, ...fromCache) } private static setupGeojsonSource( @@ -56,6 +59,10 @@ export default class LayoutSource extends FeatureSourceMerger { isActive?: Store ): FeatureSource { const source = layer.source + isActive = mapProperties.zoom.map( + (z) => (isActive?.data ?? true) && z >= layer.maxzoom, + [isActive] + ) if (source.geojsonZoomLevel === undefined) { // This is a 'load everything at once' geojson layer return new GeoJsonSource(layer, { isActive }) diff --git a/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts b/Logic/FeatureSource/Sources/OsmFeatureSource.ts similarity index 98% rename from Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts rename to Logic/FeatureSource/Sources/OsmFeatureSource.ts index 2df3358111..e55db7e942 100644 --- a/Logic/FeatureSource/TiledFeatureSource/OsmFeatureSource.ts +++ b/Logic/FeatureSource/Sources/OsmFeatureSource.ts @@ -108,6 +108,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger { } private async LoadTile(z, x, y): Promise { + console.log("OsmFeatureSource: loading ", z, x, y) if (z >= 22) { throw "This is an absurd high zoom level" } @@ -126,7 +127,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger { let error = undefined try { - const osmJson = await Utils.downloadJson(url) + const osmJson = await Utils.downloadJsonCached(url, 2000) try { this.rawDataHandlers.forEach((handler) => handler(osmJson, Tiles.tile_index(z, x, y)) diff --git a/Logic/Actors/OverpassFeatureSource.ts b/Logic/FeatureSource/Sources/OverpassFeatureSource.ts similarity index 88% rename from Logic/Actors/OverpassFeatureSource.ts rename to Logic/FeatureSource/Sources/OverpassFeatureSource.ts index acccf872e3..5e13f02ccd 100644 --- a/Logic/Actors/OverpassFeatureSource.ts +++ b/Logic/FeatureSource/Sources/OverpassFeatureSource.ts @@ -1,13 +1,13 @@ -import { ImmutableStore, Store, UIEventSource } from "../UIEventSource" -import { Or } from "../Tags/Or" -import { Overpass } from "../Osm/Overpass" -import FeatureSource from "../FeatureSource/FeatureSource" -import { Utils } from "../../Utils" -import { TagsFilter } from "../Tags/TagsFilter" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import { BBox } from "../BBox" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { Feature } from "geojson" +import FeatureSource from "../FeatureSource" +import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" +import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" +import { Or } from "../../Tags/Or" +import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig" +import { Overpass } from "../../Osm/Overpass" +import { Utils } from "../../../Utils" +import { TagsFilter } from "../../Tags/TagsFilter" +import { BBox } from "../../BBox" /** * A wrapper around the 'Overpass'-object. @@ -99,7 +99,11 @@ export default class OverpassFeatureSource implements FeatureSource { ) { return undefined } - const [bounds, date, updatedLayers] = await this.updateAsync() + const result = await this.updateAsync() + if (!result) { + return + } + const [bounds, date, updatedLayers] = result this._lastQueryBBox = bounds } @@ -188,6 +192,9 @@ export default class OverpassFeatureSource implements FeatureSource { if (data === undefined) { return undefined } + // Some metatags are delivered by overpass _without_ underscore-prefix; we fix them below + // TODO FIXME re-enable this data.features.forEach((f) => SimpleMetaTaggers.objectMetaInfo.applyMetaTagsOnFeature(f)) + self.features.setData(data.features) return [bounds, date, layersToDownload] } catch (e) { diff --git a/Logic/FeatureSource/Sources/RememberingSource.ts b/Logic/FeatureSource/Sources/RememberingSource.ts deleted file mode 100644 index fcc4b7f016..0000000000 --- a/Logic/FeatureSource/Sources/RememberingSource.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Every previously added point is remembered, but new points are added. - * Data coming from upstream will always overwrite a previous value - */ -import FeatureSource, { Tiled } from "../FeatureSource" -import { Store, UIEventSource } from "../../UIEventSource" -import { BBox } from "../../BBox" -import { Feature } from "geojson" - -export default class RememberingSource implements FeatureSource, Tiled { - public readonly features: Store - public readonly tileIndex: number - public readonly bbox: BBox - - constructor(source: FeatureSource & Tiled) { - const self = this - this.tileIndex = source.tileIndex - this.bbox = source.bbox - - const empty = [] - const featureSource = new UIEventSource(empty) - this.features = featureSource - source.features.addCallbackAndRunD((features) => { - const oldFeatures = self.features?.data ?? empty - // Then new ids - const ids = new Set(features.map((f) => f.properties.id + f.geometry.type)) - // the old data - const oldData = oldFeatures.filter( - (old) => !ids.has(old.feature.properties.id + old.feature.geometry.type) - ) - featureSource.setData([...features, ...oldData]) - }) - } -} diff --git a/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts b/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts new file mode 100644 index 0000000000..2fb3fd0a2f --- /dev/null +++ b/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts @@ -0,0 +1,29 @@ +import FeatureSource, { FeatureSourceForLayer } from "../FeatureSource" +import StaticFeatureSource from "./StaticFeatureSource" +import { GeoOperations } from "../../GeoOperations" +import { BBox } from "../../BBox" +import exp from "constants" +import FilteredLayer from "../../../Models/FilteredLayer" + +/** + * Results in a feature source which has all the elements that touch the given features + */ +export default class BBoxFeatureSource extends StaticFeatureSource { + constructor(features: FeatureSource, mustTouch: BBox) { + const bbox = mustTouch.asGeoJson({}) + super( + features.features.mapD((features) => + features.filter((feature) => GeoOperations.intersect(feature, bbox) !== undefined) + ) + ) + } +} + +export class BBoxFeatureSourceForLayer extends BBoxFeatureSource implements FeatureSourceForLayer { + constructor(features: FeatureSourceForLayer, mustTouch: BBox) { + super(features, mustTouch) + this.layer = features.layer + } + + readonly layer: FilteredLayer +} diff --git a/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts b/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts index 30ed0cb377..cb787289c1 100644 --- a/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts @@ -84,7 +84,9 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource { }) }, mapProperties, - { isActive: options.isActive } + { + isActive: options?.isActive, + } ) } } diff --git a/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts b/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts index 3b3953396e..65c249984e 100644 --- a/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts @@ -5,7 +5,8 @@ import FeatureSource from "../FeatureSource" import FeatureSourceMerger from "../Sources/FeatureSourceMerger" /*** - * A tiled source which dynamically loads the required tiles at a fixed zoom level + * A tiled source which dynamically loads the required tiles at a fixed zoom level. + * A single featureSource will be initiliased for every tile in view; which will alter be merged into this featureSource */ export default class DynamicTileSource extends FeatureSourceMerger { constructor( diff --git a/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts b/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts index 13225ed152..a1f35ac5b1 100644 --- a/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts @@ -1,11 +1,13 @@ -import TileHierarchy from "./TileHierarchy" import FeatureSource, { FeatureSourceForLayer, Tiled } from "../FeatureSource" import { OsmNode, OsmObject, OsmWay } from "../../Osm/OsmObject" import SimpleFeatureSource from "../Sources/SimpleFeatureSource" import FilteredLayer from "../../../Models/FilteredLayer" import { UIEventSource } from "../../UIEventSource" +import { OsmTags } from "../../../Models/OsmFeature"; +import { BBox } from "../../BBox"; +import { Feature, Point } from "geojson"; -export default class FullNodeDatabaseSource implements TileHierarchy { +export default class FullNodeDatabaseSource { public readonly loadedTiles = new Map() private readonly onTileLoaded: (tile: Tiled & FeatureSourceForLayer) => void private readonly layer: FilteredLayer @@ -81,4 +83,9 @@ export default class FullNodeDatabaseSource implements TileHierarchy { return this.parentWays.get(nodeId) } + + getNodesWithin(bBox: BBox) : Feature[]{ + // TODO + throw "TODO" + } } diff --git a/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts b/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts new file mode 100644 index 0000000000..96bbb52755 --- /dev/null +++ b/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts @@ -0,0 +1,28 @@ +import DynamicTileSource from "./DynamicTileSource" +import { Store } from "../../UIEventSource" +import { BBox } from "../../BBox" +import TileLocalStorage from "../Actors/TileLocalStorage" +import { Feature } from "geojson" +import StaticFeatureSource from "../Sources/StaticFeatureSource" + +export default class LocalStorageFeatureSource extends DynamicTileSource { + constructor( + layername: string, + zoomlevel: number, + mapProperties: { + bounds: Store + zoom: Store + }, + options?: { + isActive?: Store + } + ) { + const storage = TileLocalStorage.construct(layername) + super( + zoomlevel, + (tileIndex) => new StaticFeatureSource(storage.getTileSource(tileIndex)), + mapProperties, + options + ) + } +} diff --git a/Logic/FeatureSource/TiledFeatureSource/README.md b/Logic/FeatureSource/TiledFeatureSource/README.md deleted file mode 100644 index 914c1caf7d..0000000000 --- a/Logic/FeatureSource/TiledFeatureSource/README.md +++ /dev/null @@ -1,24 +0,0 @@ -Data in MapComplete can come from multiple sources. - -Currently, they are: - -- The Overpass-API -- The OSM-API -- One or more GeoJSON files. This can be a single file or a set of tiled geojson files -- LocalStorage, containing features from a previous visit -- Changes made by the user introducing new features - -When the data enters from Overpass or from the OSM-API, they are first distributed per layer: - -OVERPASS | ---PerLayerFeatureSource---> FeatureSourceForLayer[] -OSM | - -The GeoJSon files (not tiled) are then added to this list - -A single FeatureSourcePerLayer is then further handled by splitting it into a tile hierarchy. - -In order to keep thins snappy, they are distributed over a tiled database per layer. - -## Notes - -`cached-featuresbookcases` is the old key used `cahced-features{themeid}` and should be cleaned up \ No newline at end of file diff --git a/Logic/FeatureSource/TiledFeatureSource/TileHierarchy.ts b/Logic/FeatureSource/TiledFeatureSource/TileHierarchy.ts deleted file mode 100644 index c318fe5e1d..0000000000 --- a/Logic/FeatureSource/TiledFeatureSource/TileHierarchy.ts +++ /dev/null @@ -1,24 +0,0 @@ -import FeatureSource, { Tiled } from "../FeatureSource" -import { BBox } from "../../BBox" - -export default interface TileHierarchy { - /** - * A mapping from 'tile_index' to the actual tile featrues - */ - loadedTiles: Map -} - -export class TileHierarchyTools { - public static getTiles( - hierarchy: TileHierarchy, - bbox: BBox - ): T[] { - const result: T[] = [] - hierarchy.loadedTiles.forEach((tile) => { - if (tile.bbox.overlapsWith(bbox)) { - result.push(tile) - } - }) - return result - } -} diff --git a/Logic/FeatureSource/TiledFeatureSource/TileHierarchyMerger.ts b/Logic/FeatureSource/TiledFeatureSource/TileHierarchyMerger.ts deleted file mode 100644 index b4de5333d7..0000000000 --- a/Logic/FeatureSource/TiledFeatureSource/TileHierarchyMerger.ts +++ /dev/null @@ -1,58 +0,0 @@ -import TileHierarchy from "./TileHierarchy" -import { UIEventSource } from "../../UIEventSource" -import FeatureSource, { FeatureSourceForLayer, IndexedFeatureSource, Tiled } from "../FeatureSource" -import FilteredLayer from "../../../Models/FilteredLayer" -import FeatureSourceMerger from "../Sources/FeatureSourceMerger" -import { Tiles } from "../../../Models/TileRange" -import { BBox } from "../../BBox" - -export class TileHierarchyMerger implements TileHierarchy { - public readonly loadedTiles: Map = new Map< - number, - FeatureSourceForLayer & Tiled - >() - public readonly layer: FilteredLayer - private readonly sources: Map> = new Map< - number, - UIEventSource - >() - private _handleTile: (src: FeatureSourceForLayer & IndexedFeatureSource, index: number) => void - - constructor( - layer: FilteredLayer, - handleTile: ( - src: FeatureSourceForLayer & IndexedFeatureSource & Tiled, - index: number - ) => void - ) { - this.layer = layer - this._handleTile = handleTile - } - - /** - * Add another feature source for the given tile. - * Entries for this tile will be merged - * @param src - */ - public registerTile(src: FeatureSource & Tiled) { - const index = src.tileIndex - if (this.sources.has(index)) { - const sources = this.sources.get(index) - sources.data.push(src) - sources.ping() - return - } - - // We have to setup - const sources = new UIEventSource([src]) - this.sources.set(index, sources) - const merger = new FeatureSourceMerger( - this.layer, - index, - BBox.fromTile(...Tiles.tile_from_index(index)), - sources - ) - this.loadedTiles.set(index, merger) - this._handleTile(merger, index) - } -} diff --git a/Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource.ts b/Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource.ts deleted file mode 100644 index d49bf16d3e..0000000000 --- a/Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource.ts +++ /dev/null @@ -1,249 +0,0 @@ -import FeatureSource, { FeatureSourceForLayer, IndexedFeatureSource, Tiled } from "../FeatureSource" -import { Store, UIEventSource } from "../../UIEventSource" -import FilteredLayer from "../../../Models/FilteredLayer" -import TileHierarchy from "./TileHierarchy" -import { Tiles } from "../../../Models/TileRange" -import { BBox } from "../../BBox" -import { Feature } from "geojson"; - -/** - * Contains all features in a tiled fashion. - * The data will be automatically broken down into subtiles when there are too much features in a single tile or if the zoomlevel is too high - */ -export default class TiledFeatureSource - implements - Tiled, - IndexedFeatureSource, - FeatureSourceForLayer, - TileHierarchy -{ - public readonly z: number - public readonly x: number - public readonly y: number - public readonly parent: TiledFeatureSource - public readonly root: TiledFeatureSource - public readonly layer: FilteredLayer - /* An index of all known tiles. allTiles[z][x][y].get('layerid') will yield the corresponding tile. - * Only defined on the root element! - */ - public readonly loadedTiles: Map = undefined - - public readonly maxFeatureCount: number - public readonly name - public readonly features: UIEventSource - public readonly containedIds: Store> - - public readonly bbox: BBox - public readonly tileIndex: number - private upper_left: TiledFeatureSource - private upper_right: TiledFeatureSource - private lower_left: TiledFeatureSource - private lower_right: TiledFeatureSource - private readonly maxzoom: number - private readonly options: TiledFeatureSourceOptions - - private constructor( - z: number, - x: number, - y: number, - parent: TiledFeatureSource, - options?: TiledFeatureSourceOptions - ) { - this.z = z - this.x = x - this.y = y - this.bbox = BBox.fromTile(z, x, y) - this.tileIndex = Tiles.tile_index(z, x, y) - this.name = `TiledFeatureSource(${z},${x},${y})` - this.parent = parent - this.layer = options.layer - options = options ?? {} - this.maxFeatureCount = options?.maxFeatureCount ?? 250 - this.maxzoom = options.maxZoomLevel ?? 18 - this.options = options - if (parent === undefined) { - throw "Parent is not allowed to be undefined. Use null instead" - } - if (parent === null && z !== 0 && x !== 0 && y !== 0) { - throw "Invalid root tile: z, x and y should all be null" - } - if (parent === null) { - this.root = this - this.loadedTiles = new Map() - } else { - this.root = this.parent.root - this.loadedTiles = this.root.loadedTiles - const i = Tiles.tile_index(z, x, y) - this.root.loadedTiles.set(i, this) - } - this.features = new UIEventSource([]) - this.containedIds = this.features.map((features) => { - if (features === undefined) { - return undefined - } - return new Set(features.map((f) => f.properties.id)) - }) - - // We register this tile, but only when there is some data in it - if (this.options.registerTile !== undefined) { - this.features.addCallbackAndRunD((features) => { - if (features.length === 0) { - return - } - this.options.registerTile(this) - return true - }) - } - } - - public static createHierarchy( - features: FeatureSource, - options?: TiledFeatureSourceOptions - ): TiledFeatureSource { - options = { - ...options, - layer: features["layer"] ?? options.layer, - } - const root = new TiledFeatureSource(0, 0, 0, null, options) - features.features?.addCallbackAndRunD((feats) => root.addFeatures(feats)) - return root - } - - private isSplitNeeded(featureCount: number) { - if (this.upper_left !== undefined) { - // This tile has been split previously, so we keep on splitting - return true - } - if (this.z >= this.maxzoom) { - // We are not allowed to split any further - return false - } - if (this.options.minZoomLevel !== undefined && this.z < this.options.minZoomLevel) { - // We must have at least this zoom level before we are allowed to start splitting - return true - } - - // To much features - we split - return featureCount > this.maxFeatureCount - } - - /*** - * Adds the list of features to this hierarchy. - * If there are too much features, the list will be broken down and distributed over the subtiles (only retaining features that don't fit a subtile on this level) - * @param features - * @private - */ - private addFeatures(features: Feature[]) { - if (features === undefined || features.length === 0) { - return - } - - if (!this.isSplitNeeded(features.length)) { - this.features.setData(features) - return - } - - if (this.upper_left === undefined) { - this.upper_left = new TiledFeatureSource( - this.z + 1, - this.x * 2, - this.y * 2, - this, - this.options - ) - this.upper_right = new TiledFeatureSource( - this.z + 1, - this.x * 2 + 1, - this.y * 2, - this, - this.options - ) - this.lower_left = new TiledFeatureSource( - this.z + 1, - this.x * 2, - this.y * 2 + 1, - this, - this.options - ) - this.lower_right = new TiledFeatureSource( - this.z + 1, - this.x * 2 + 1, - this.y * 2 + 1, - this, - this.options - ) - } - - const ulf = [] - const urf = [] - const llf = [] - const lrf = [] - const overlapsboundary = [] - - for (const feature of features) { - const bbox = BBox.get(feature) - - // There are a few strategies to deal with features that cross tile boundaries - - if (this.options.noDuplicates) { - // Strategy 1: We put the feature into a somewhat matching tile - if (bbox.overlapsWith(this.upper_left.bbox)) { - ulf.push(feature) - } else if (bbox.overlapsWith(this.upper_right.bbox)) { - urf.push(feature) - } else if (bbox.overlapsWith(this.lower_left.bbox)) { - llf.push(feature) - } else if (bbox.overlapsWith(this.lower_right.bbox)) { - lrf.push(feature) - } else { - overlapsboundary.push(feature) - } - } else if (this.options.minZoomLevel === undefined) { - // Strategy 2: put it into a strictly matching tile (or in this tile, which is slightly too big) - if (bbox.isContainedIn(this.upper_left.bbox)) { - ulf.push(feature) - } else if (bbox.isContainedIn(this.upper_right.bbox)) { - urf.push(feature) - } else if (bbox.isContainedIn(this.lower_left.bbox)) { - llf.push(feature) - } else if (bbox.isContainedIn(this.lower_right.bbox)) { - lrf.push(feature) - } else { - overlapsboundary.push(feature) - } - } else { - // Strategy 3: We duplicate a feature on a boundary into every tile as we need to get to the minZoomLevel - if (bbox.overlapsWith(this.upper_left.bbox)) { - ulf.push(feature) - } - if (bbox.overlapsWith(this.upper_right.bbox)) { - urf.push(feature) - } - if (bbox.overlapsWith(this.lower_left.bbox)) { - llf.push(feature) - } - if (bbox.overlapsWith(this.lower_right.bbox)) { - lrf.push(feature) - } - } - } - this.upper_left.addFeatures(ulf) - this.upper_right.addFeatures(urf) - this.lower_left.addFeatures(llf) - this.lower_right.addFeatures(lrf) - this.features.setData(overlapsboundary) - } -} - -export interface TiledFeatureSourceOptions { - readonly maxFeatureCount?: number - readonly maxZoomLevel?: number - readonly minZoomLevel?: number - /** - * IF minZoomLevel is set, and if a feature runs through a tile boundary, it would normally be duplicated. - * Setting 'dontEnforceMinZoomLevel' will assign to feature to some matching subtile. - */ - readonly noDuplicates?: boolean - readonly registerTile?: (tile: TiledFeatureSource & FeatureSourceForLayer & Tiled) => void - readonly layer?: FilteredLayer -} diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 7957fb8cd4..302e809a38 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -2,19 +2,34 @@ import { BBox } from "./BBox" import LayerConfig from "../Models/ThemeConfig/LayerConfig" import * as turf from "@turf/turf" import { AllGeoJSON, booleanWithin, Coord } from "@turf/turf" -import { Feature, Geometry, MultiPolygon, Polygon } from "geojson" -import { GeoJSON, LineString, Point, Position } from "geojson" +import { + Feature, + GeoJSON, + Geometry, + LineString, + MultiPolygon, + Point, + Polygon, + Position, +} from "geojson" import togpx from "togpx" import Constants from "../Models/Constants" +import { Tiles } from "../Models/TileRange" export class GeoOperations { + private static readonly _earthRadius = 6378137 + private static readonly _originShift = (2 * Math.PI * GeoOperations._earthRadius) / 2 + /** * Create a union between two features */ - static union = turf.union - static intersect = turf.intersect - private static readonly _earthRadius = 6378137 - private static readonly _originShift = (2 * Math.PI * GeoOperations._earthRadius) / 2 + public static union(f0: Feature, f1: Feature): Feature | null { + return turf.union(f0, f1) + } + + public static intersect(f0: Feature, f1: Feature): Feature | null { + return turf.intersect(f0, f1) + } static surfaceAreaInSqMeters(feature: any) { return turf.area(feature) @@ -637,14 +652,14 @@ export class GeoOperations { */ static completelyWithin( feature: Feature, - possiblyEncloingFeature: Feature + possiblyEnclosingFeature: Feature ): boolean { - return booleanWithin(feature, possiblyEncloingFeature) + return booleanWithin(feature, possiblyEnclosingFeature) } /** * Create an intersection between two features. - * A new feature is returned based on 'toSplit', which'll have a geometry that is completely withing boundary + * One or multiple new feature is returned based on 'toSplit', which'll have a geometry that is completely withing boundary */ public static clipWith(toSplit: Feature, boundary: Feature): Feature[] { if (toSplit.geometry.type === "Point") { @@ -677,35 +692,6 @@ export class GeoOperations { throw "Invalid geometry type with GeoOperations.clipWith: " + toSplit.geometry.type } - /** - * Helper function which does the heavy lifting for 'inside' - */ - private static pointInPolygonCoordinates( - x: number, - y: number, - coordinates: [number, number][][] - ): boolean { - const inside = GeoOperations.pointWithinRing( - x, - y, - /*This is the outer ring of the polygon */ coordinates[0] - ) - if (!inside) { - return false - } - for (let i = 1; i < coordinates.length; i++) { - const inHole = GeoOperations.pointWithinRing( - x, - y, - coordinates[i] /* These are inner rings, aka holes*/ - ) - if (inHole) { - return false - } - } - return true - } - /** * * @@ -763,6 +749,62 @@ export class GeoOperations { throw "Unkown location type: " + location } } + + /** + * Constructs all tiles where features overlap with and puts those features in them. + * Long features (e.g. lines or polygons) which overlap with multiple tiles are referenced in each tile they overlap with + * @param zoomlevel + * @param features + */ + public static slice(zoomlevel: number, features: Feature[]): Map { + const tiles = new Map() + + for (const feature of features) { + const bbox = BBox.get(feature) + Tiles.MapRange(Tiles.tileRangeFrom(bbox, zoomlevel), (x, y) => { + const i = Tiles.tile_index(zoomlevel, x, y) + + let tiledata = tiles.get(i) + if (tiledata === undefined) { + tiledata = [] + tiles.set(i, tiledata) + } + tiledata.push(feature) + }) + } + + return tiles + } + + /** + * Helper function which does the heavy lifting for 'inside' + */ + private static pointInPolygonCoordinates( + x: number, + y: number, + coordinates: [number, number][][] + ): boolean { + const inside = GeoOperations.pointWithinRing( + x, + y, + /*This is the outer ring of the polygon */ coordinates[0] + ) + if (!inside) { + return false + } + for (let i = 1; i < coordinates.length; i++) { + const inHole = GeoOperations.pointWithinRing( + x, + y, + coordinates[i] /* These are inner rings, aka holes*/ + ) + if (inHole) { + return false + } + } + return true + } + private static pointWithinRing(x: number, y: number, ring: [number, number][]) { let inside = false for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) { diff --git a/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts b/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts index 9e46a3791a..353e94b1cb 100644 --- a/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts +++ b/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts @@ -2,12 +2,12 @@ import { OsmCreateAction } from "./OsmChangeAction" import { Tag } from "../../Tags/Tag" import { Changes } from "../Changes" import { ChangeDescription } from "./ChangeDescription" -import FeaturePipelineState from "../../State/FeaturePipelineState" -import FeatureSource from "../../FeatureSource/FeatureSource" import CreateNewWayAction from "./CreateNewWayAction" import CreateWayWithPointReuseAction, { MergePointConfig } from "./CreateWayWithPointReuseAction" import { And } from "../../Tags/And" import { TagUtils } from "../../Tags/TagUtils" +import { SpecialVisualizationState } from "../../../UI/SpecialVisualization" +import FeatureSource from "../../FeatureSource/FeatureSource" /** * More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points @@ -26,14 +26,14 @@ export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAct tags: Tag[], outerRingCoordinates: [number, number][], innerRingsCoordinates: [number, number][][], - state: FeaturePipelineState, + state: SpecialVisualizationState, config: MergePointConfig[], changeType: "import" | "create" | string ) { super(null, true) this._tags = [...tags, new Tag("type", "multipolygon")] this.changeType = changeType - this.theme = state?.layoutToUse?.id ?? "" + this.theme = state?.layout?.id ?? "" this.createOuterWay = new CreateWayWithPointReuseAction( [], outerRingCoordinates, @@ -45,7 +45,7 @@ export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAct new CreateNewWayAction( [], ringCoordinates.map(([lon, lat]) => ({ lat, lon })), - { theme: state?.layoutToUse?.id } + { theme: state?.layout?.id } ) ) @@ -59,6 +59,10 @@ export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAct } } + public async getPreview(): Promise { + return undefined + } + protected async CreateChangeDescriptions(changes: Changes): Promise { console.log("Running CMPWPRA") const descriptions: ChangeDescription[] = [] diff --git a/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts b/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts index ed549d4afd..df99cc97c8 100644 --- a/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts +++ b/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts @@ -2,7 +2,6 @@ import { OsmCreateAction } from "./OsmChangeAction" import { Tag } from "../../Tags/Tag" import { Changes } from "../Changes" import { ChangeDescription } from "./ChangeDescription" -import FeaturePipelineState from "../../State/FeaturePipelineState" import { BBox } from "../../BBox" import { TagsFilter } from "../../Tags/TagsFilter" import { GeoOperations } from "../../GeoOperations" @@ -10,6 +9,7 @@ import FeatureSource from "../../FeatureSource/FeatureSource" import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource" import CreateNewNodeAction from "./CreateNewNodeAction" import CreateNewWayAction from "./CreateNewWayAction" +import { SpecialVisualizationState } from "../../../UI/SpecialVisualization" export interface MergePointConfig { withinRangeOfM: number @@ -62,14 +62,14 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { * lngLat-coordinates * @private */ - private _coordinateInfo: CoordinateInfo[] - private _state: FeaturePipelineState - private _config: MergePointConfig[] + private readonly _coordinateInfo: CoordinateInfo[] + private readonly _state: SpecialVisualizationState + private readonly _config: MergePointConfig[] constructor( tags: Tag[], coordinates: [number, number][], - state: FeaturePipelineState, + state: SpecialVisualizationState, config: MergePointConfig[] ) { super(null, true) @@ -188,7 +188,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { } public async CreateChangeDescriptions(changes: Changes): Promise { - const theme = this._state?.layoutToUse?.id + const theme = this._state?.layout?.id const allChanges: ChangeDescription[] = [] const nodeIdsToUse: { lat: number; lon: number; nodeId?: number }[] = [] for (let i = 0; i < this._coordinateInfo.length; i++) { @@ -252,9 +252,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { private CalculateClosebyNodes(coordinates: [number, number][]): CoordinateInfo[] { const bbox = new BBox(coordinates) const state = this._state - const allNodes = [].concat( - ...(state?.featurePipeline?.GetFeaturesWithin("type_node", bbox.pad(1.2)) ?? []) - ) + const allNodes =state.fullNodeDatabase?.getNodesWithin(bbox.pad(1.2)) const maxDistance = Math.max(...this._config.map((c) => c.withinRangeOfM)) // Init coordianteinfo with undefined but the same length as coordinates diff --git a/Logic/Osm/Actions/ReplaceGeometryAction.ts b/Logic/Osm/Actions/ReplaceGeometryAction.ts index 94390f61f4..3ec65299ff 100644 --- a/Logic/Osm/Actions/ReplaceGeometryAction.ts +++ b/Logic/Osm/Actions/ReplaceGeometryAction.ts @@ -12,8 +12,8 @@ import { And } from "../../Tags/And" import { Utils } from "../../../Utils" import { OsmConnection } from "../OsmConnection" import { Feature } from "@turf/turf" -import FeaturePipeline from "../../FeatureSource/FeaturePipeline" -import { Geometry, LineString, Point, Polygon } from "geojson" +import { Geometry, LineString, Point } from "geojson" +import FullNodeDatabaseSource from "../../FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" export default class ReplaceGeometryAction extends OsmChangeAction { /** @@ -22,7 +22,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction { private readonly feature: any private readonly state: { osmConnection: OsmConnection - featurePipeline: FeaturePipeline + fullNodeDatabase?: FullNodeDatabaseSource } private readonly wayToReplaceId: string private readonly theme: string @@ -41,7 +41,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction { constructor( state: { osmConnection: OsmConnection - featurePipeline: FeaturePipeline + fullNodeDatabase?: FullNodeDatabaseSource }, feature: any, wayToReplaceId: string, @@ -195,7 +195,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction { }> { // TODO FIXME: if a new point has to be created, snap to already existing ways - const nodeDb = this.state.featurePipeline.fullNodeDatabase + const nodeDb = this.state.fullNodeDatabase if (nodeDb === undefined) { throw "PANIC: replaceGeometryAction needs the FullNodeDatabase, which is undefined. This should be initialized by having the 'type_node'-layer enabled in your theme. (NB: the replacebutton has type_node as dependency)" } @@ -415,7 +415,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction { } protected async CreateChangeDescriptions(changes: Changes): Promise { - const nodeDb = this.state.featurePipeline.fullNodeDatabase + const nodeDb = this.state.fullNodeDatabase if (nodeDb === undefined) { throw "PANIC: replaceGeometryAction needs the FullNodeDatabase, which is undefined. This should be initialized by having the 'type_node'-layer enabled in your theme. (NB: the replacebutton has type_node as dependency)" } diff --git a/Logic/Osm/Geocoding.ts b/Logic/Osm/Geocoding.ts index d0241608cb..09da7af6dc 100644 --- a/Logic/Osm/Geocoding.ts +++ b/Logic/Osm/Geocoding.ts @@ -5,6 +5,10 @@ export interface GeoCodeResult { display_name: string lat: number lon: number + /** + * Format: + * [lat, lat, lon, lon] + */ boundingbox: number[] osm_type: "node" | "way" | "relation" osm_id: string diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index 4d23d1f6c8..c68040a1aa 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -15,6 +15,13 @@ import { OsmTags } from "../Models/OsmFeature" import { UIEventSource } from "./UIEventSource" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" +/** + * All elements that are needed to perform metatagging + */ +export interface MetataggingState { + layout: LayoutConfig +} + export abstract class SimpleMetaTagger { public readonly keys: string[] public readonly doc: string @@ -60,7 +67,7 @@ export abstract class SimpleMetaTagger { feature: any, layer: LayerConfig, tagsStore: UIEventSource>, - state: { layout: LayoutConfig } + state: MetataggingState ): boolean } @@ -119,7 +126,7 @@ export class CountryTagger extends SimpleMetaTagger { }) } - applyMetaTagsOnFeature(feature, _, state) { + applyMetaTagsOnFeature(feature, _, tagsSource) { let centerPoint: any = GeoOperations.centerpoint(feature) const runningTasks = this.runningTasks const lat = centerPoint.geometry.coordinates[1] @@ -128,28 +135,29 @@ export class CountryTagger extends SimpleMetaTagger { CountryTagger.coder .GetCountryCodeAsync(lon, lat) .then((countries) => { - runningTasks.delete(feature) - try { - const oldCountry = feature.properties["_country"] - feature.properties["_country"] = countries[0].trim().toLowerCase() - if (oldCountry !== feature.properties["_country"]) { - const tagsSource = state?.allElements?.getEventSourceById( - feature.properties.id - ) - tagsSource?.ping() - } - } catch (e) { - console.warn(e) + const oldCountry = feature.properties["_country"] + const newCountry = countries[0].trim().toLowerCase() + if (oldCountry !== newCountry) { + tagsSource.data["_country"] = newCountry + tagsSource?.ping() } }) - .catch((_) => { - runningTasks.delete(feature) + .catch((e) => { + console.warn(e) }) + .finally(() => runningTasks.delete(feature)) return false } } class InlineMetaTagger extends SimpleMetaTagger { + public readonly applyMetaTagsOnFeature: ( + feature: any, + layer: LayerConfig, + tagsStore: UIEventSource, + state: MetataggingState + ) => boolean + constructor( docs: { keys: string[] @@ -166,23 +174,17 @@ class InlineMetaTagger extends SimpleMetaTagger { feature: any, layer: LayerConfig, tagsStore: UIEventSource, - state: { layout: LayoutConfig } + state: MetataggingState ) => boolean ) { super(docs) this.applyMetaTagsOnFeature = f } - - public readonly applyMetaTagsOnFeature: ( - feature: any, - layer: LayerConfig, - tagsStore: UIEventSource, - state: { layout: LayoutConfig } - ) => boolean } -export default class SimpleMetaTaggers { - public static readonly objectMetaInfo = new InlineMetaTagger( - { + +export class RewriteMetaInfoTags extends SimpleMetaTagger { + constructor() { + super({ keys: [ "_last_edit:contributor", "_last_edit:contributor:uid", @@ -192,30 +194,37 @@ export default class SimpleMetaTaggers { "_backend", ], doc: "Information about the last edit of this object.", - }, - (feature) => { - /*Note: also called by 'UpdateTagsFromOsmAPI'*/ + }) + } - const tgs = feature.properties - let movedSomething = false + applyMetaTagsOnFeature(feature: Feature): boolean { + /*Note: also called by 'UpdateTagsFromOsmAPI'*/ - function move(src: string, target: string) { - if (tgs[src] === undefined) { - return - } - tgs[target] = tgs[src] - delete tgs[src] - movedSomething = true + const tgs = feature.properties + let movedSomething = false + + function move(src: string, target: string) { + if (tgs[src] === undefined) { + return } - - move("user", "_last_edit:contributor") - move("uid", "_last_edit:contributor:uid") - move("changeset", "_last_edit:changeset") - move("timestamp", "_last_edit:timestamp") - move("version", "_version_number") - return movedSomething + tgs[target] = tgs[src] + delete tgs[src] + movedSomething = true } - ) + + move("user", "_last_edit:contributor") + move("uid", "_last_edit:contributor:uid") + move("changeset", "_last_edit:changeset") + move("timestamp", "_last_edit:timestamp") + move("version", "_version_number") + return movedSomething + } +} +export default class SimpleMetaTaggers { + /** + * A simple metatagger which rewrites various metatags as needed + */ + public static readonly objectMetaInfo = new RewriteMetaInfoTags() public static country = new CountryTagger() public static geometryType = new InlineMetaTagger( { diff --git a/Logic/State/FeaturePipelineState.ts b/Logic/State/FeaturePipelineState.ts index 9de67d4c12..a63ad9028e 100644 --- a/Logic/State/FeaturePipelineState.ts +++ b/Logic/State/FeaturePipelineState.ts @@ -1,10 +1,5 @@ -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import FeaturePipeline from "../FeatureSource/FeaturePipeline" -import { Tiles } from "../../Models/TileRange" import SelectedFeatureHandler from "../Actors/SelectedFeatureHandler" import Hash from "../Web/Hash" -import { BBox } from "../BBox" -import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource" import MetaTagRecalculator from "../FeatureSource/Actors/MetaTagRecalculator" export default class FeaturePipelineState { @@ -14,101 +9,9 @@ export default class FeaturePipelineState { public readonly featurePipeline: FeaturePipeline private readonly metatagRecalculator: MetaTagRecalculator - constructor(layoutToUse: LayoutConfig) { - const clustering = layoutToUse?.clustering - const clusterCounter = this.featureAggregator - const self = this - - /** - * We are a bit in a bind: - * There is the featurePipeline, which creates some sources during construction - * THere is the metatagger, which needs to have these sources registered AND which takes a FeaturePipeline as argument - * - * This is a bit of a catch-22 (except that it isn't) - * The sources that are registered in the constructor are saved into 'registeredSources' temporary - * - */ - const sourcesToRegister = [] - - function registerRaw(source: FeatureSourceForLayer & Tiled) { - if (self.metatagRecalculator === undefined) { - sourcesToRegister.push(source) - } else { - self.metatagRecalculator.registerSource(source) - } - } - - function registerSource(source: FeatureSourceForLayer & Tiled) { - clusterCounter.addTile(source) - const sourceBBox = source.features.map((allFeatures) => - BBox.bboxAroundAll(allFeatures.map(BBox.get)) - ) - - // Do show features indicates if the respective 'showDataLayer' should be shown. It can be hidden by e.g. clustering - source.features.map( - (f) => { - const z = self.locationControl.data.zoom - - if (!source.layer.isDisplayed.data) { - return false - } - - const bounds = self.currentBounds.data - if (bounds === undefined) { - // Map is not yet displayed - return false - } - - if (!sourceBBox.data.overlapsWith(bounds)) { - // Not within range -> features are hidden - return false - } - - if (z < source.layer.layerDef.minzoom) { - // Layer is always hidden for this zoom level - return false - } - - if (z > clustering.maxZoom) { - return true - } - - if (f.length > clustering.minNeededElements) { - // This tile alone already has too much features - return false - } - - let [tileZ, tileX, tileY] = Tiles.tile_from_index(source.tileIndex) - if (tileZ >= z) { - while (tileZ > z) { - tileZ-- - tileX = Math.floor(tileX / 2) - tileY = Math.floor(tileY / 2) - } - - if ( - clusterCounter.getTile(Tiles.tile_index(tileZ, tileX, tileY)) - ?.totalValue > clustering.minNeededElements - ) { - // To much elements - return false - } - } - - return true - }, - [self.currentBounds, source.layer.isDisplayed, sourceBBox] - ) - } - - this.featurePipeline = new FeaturePipeline(registerSource, this, { - handleRawFeatureSource: registerRaw, - }) + constructor() { this.metatagRecalculator = new MetaTagRecalculator(this, this.featurePipeline) this.metatagRecalculator.registerSource(this.currentView) - - sourcesToRegister.forEach((source) => self.metatagRecalculator.registerSource(source)) - new SelectedFeatureHandler(Hash.hash, this) } } diff --git a/Logic/State/LayerState.ts b/Logic/State/LayerState.ts index 46fd6989c5..c2061e7f94 100644 --- a/Logic/State/LayerState.ts +++ b/Logic/State/LayerState.ts @@ -1,10 +1,8 @@ import { UIEventSource } from "../UIEventSource" import { GlobalFilter } from "../../Models/GlobalFilter" -import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" +import FilteredLayer from "../../Models/FilteredLayer" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { OsmConnection } from "../Osm/OsmConnection" -import { LocalStorageSource } from "../Web/LocalStorageSource" -import { QueryParameters } from "../Web/QueryParameters" /** * The layer state keeps track of: @@ -36,83 +34,14 @@ export default class LayerState { this.osmConnection = osmConnection this.filteredLayers = new Map() for (const layer of layers) { - this.filteredLayers.set(layer.id, this.initFilteredLayer(layer, context)) + this.filteredLayers.set( + layer.id, + FilteredLayer.initLinkedState(layer, context, this.osmConnection) + ) } layers.forEach((l) => this.linkFilterStates(l)) } - private static getPref( - osmConnection: OsmConnection, - key: string, - layer: LayerConfig - ): UIEventSource { - return osmConnection.GetPreference(key, layer.shownByDefault + "").sync( - (v) => { - if (v === undefined) { - return undefined - } - return v === "true" - }, - [], - (b) => { - if (b === undefined) { - return undefined - } - return "" + b - } - ) - } - /** - * INitializes a filtered layer for the given layer. - * @param layer - * @param context: probably the theme-name. This is used to disambiguate the user settings; e.g. when using the same layer in different contexts - * @private - */ - private initFilteredLayer(layer: LayerConfig, context: string): FilteredLayer | undefined { - let isDisplayed: UIEventSource - const osmConnection = this.osmConnection - if (layer.syncSelection === "local") { - isDisplayed = LocalStorageSource.GetParsed( - context + "-layer-" + layer.id + "-enabled", - layer.shownByDefault - ) - } else if (layer.syncSelection === "theme-only") { - isDisplayed = LayerState.getPref( - osmConnection, - context + "-layer-" + layer.id + "-enabled", - layer - ) - } else if (layer.syncSelection === "global") { - isDisplayed = LayerState.getPref(osmConnection, "layer-" + layer.id + "-enabled", layer) - } else { - isDisplayed = QueryParameters.GetBooleanQueryParameter( - "layer-" + layer.id, - layer.shownByDefault, - "Wether or not layer " + layer.id + " is shown" - ) - } - - const flayer: FilteredLayer = { - isDisplayed, - layerDef: layer, - appliedFilters: new UIEventSource>( - new Map() - ), - } - layer.filters?.forEach((filterConfig) => { - const stateSrc = filterConfig.initState() - - stateSrc.addCallbackAndRun((state) => - flayer.appliedFilters.data.set(filterConfig.id, state) - ) - flayer.appliedFilters - .map((dict) => dict.get(filterConfig.id)) - .addCallback((state) => stateSrc.setData(state)) - }) - - return flayer - } - /** * Some layers copy the filter state of another layer - this is quite often the case for 'sibling'-layers, * (where two variations of the same layer are used, e.g. a specific type of shop on all zoom levels and all shops on high zoom). @@ -136,10 +65,6 @@ export default class LayerState { console.warn( "Linking filter and isDisplayed-states of " + layer.id + " and " + layer.filterIsSameAs ) - this.filteredLayers.set(layer.id, { - isDisplayed: toReuse.isDisplayed, - layerDef: layer, - appliedFilters: toReuse.appliedFilters, - }) + this.filteredLayers.set(layer.id, toReuse) } } diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index 9f6f115a68..8f80a5046e 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -17,14 +17,10 @@ export default class UserRelatedState { The user credentials */ public osmConnection: OsmConnection - /** - THe change handler - */ - public changes: Changes /** * The key for mangrove */ - public mangroveIdentity: MangroveIdentity + public readonly mangroveIdentity: MangroveIdentity public readonly installedUserThemes: Store diff --git a/Logic/UIEventSource.ts b/Logic/UIEventSource.ts index b161f262b5..6ca8ef63c7 100644 --- a/Logic/UIEventSource.ts +++ b/Logic/UIEventSource.ts @@ -63,27 +63,10 @@ export class Stores { stable.setData(undefined) return } - const oldList = stable.data - if (oldList === list) { + if (Utils.sameList(stable.data, list)) { return } - if (oldList == list) { - return - } - if (oldList === undefined || oldList.length !== list.length) { - stable.setData(list) - return - } - - for (let i = 0; i < list.length; i++) { - if (oldList[i] !== list[i]) { - stable.setData(list) - return - } - } - - // No actual changes, so we don't do anything - return + stable.setData(list) }) return stable } @@ -93,7 +76,7 @@ export abstract class Store implements Readable { abstract readonly data: T /** - * OPtional value giving a title to the UIEventSource, mainly used for debugging + * Optional value giving a title to the UIEventSource, mainly used for debugging */ public readonly tag: string | undefined @@ -794,4 +777,14 @@ export class UIEventSource extends Store implements Writable { update(f: Updater & ((value: T) => T)): void { this.setData(f(this.data)) } + + /** + * Create a new UIEVentSource. Whenever 'source' changes, the returned UIEventSource will get this value as well. + * However, this value can be overriden without affecting source + */ + static feedFrom(store: Store): UIEventSource { + const src = new UIEventSource(store.data) + store.addCallback((t) => src.setData(t)) + return src + } } diff --git a/Logic/Web/MangroveReviews.ts b/Logic/Web/MangroveReviews.ts index a0076e0277..e2ed56e128 100644 --- a/Logic/Web/MangroveReviews.ts +++ b/Logic/Web/MangroveReviews.ts @@ -1,10 +1,8 @@ import { ImmutableStore, Store, UIEventSource } from "../UIEventSource" import { MangroveReviews, Review } from "mangrove-reviews-typescript" import { Utils } from "../../Utils" -import { Feature, Geometry, Position } from "geojson" +import { Feature, Position } from "geojson" import { GeoOperations } from "../GeoOperations" -import { OsmTags } from "../../Models/OsmFeature" -import { ElementStorage } from "../ElementStorage" export class MangroveIdentity { public readonly keypair: Store @@ -67,11 +65,9 @@ export default class FeatureReviews { private readonly _identity: MangroveIdentity private constructor( - feature: Feature, - state: { - allElements: ElementStorage - mangroveIdentity?: MangroveIdentity - }, + feature: Feature, + tagsSource: UIEventSource>, + mangroveIdentity?: MangroveIdentity, options?: { nameKey?: "name" | string fallbackName?: string @@ -80,8 +76,7 @@ export default class FeatureReviews { ) { const centerLonLat = GeoOperations.centerpointCoordinates(feature) ;[this._lon, this._lat] = centerLonLat - this._identity = - state?.mangroveIdentity ?? new MangroveIdentity(new UIEventSource(undefined)) + this._identity = mangroveIdentity ?? new MangroveIdentity(new UIEventSource(undefined)) const nameKey = options?.nameKey ?? "name" if (feature.geometry.type === "Point") { @@ -108,9 +103,7 @@ export default class FeatureReviews { this._uncertainty = options?.uncertaintyRadius ?? maxDistance } - this._name = state.allElements - .getEventSourceById(feature.properties.id) - .map((tags) => tags[nameKey] ?? options?.fallbackName) + this._name = tagsSource .map((tags) => tags[nameKey] ?? options?.fallbackName) this.subjectUri = this.ConstructSubjectUri() @@ -136,11 +129,9 @@ export default class FeatureReviews { * Construct a featureReviewsFor or fetches it from the cache */ public static construct( - feature: Feature, - state: { - allElements: ElementStorage - mangroveIdentity?: MangroveIdentity - }, + feature: Feature, + tagsSource: UIEventSource>, + mangroveIdentity?: MangroveIdentity, options?: { nameKey?: "name" | string fallbackName?: string @@ -152,7 +143,7 @@ export default class FeatureReviews { if (cached !== undefined) { return cached } - const featureReviews = new FeatureReviews(feature, state, options) + const featureReviews = new FeatureReviews(feature, tagsSource, mangroveIdentity, options) FeatureReviews._featureReviewsCache[key] = featureReviews return featureReviews } diff --git a/Models/FilteredLayer.ts b/Models/FilteredLayer.ts index 3263c35aee..1cbe82a586 100644 --- a/Models/FilteredLayer.ts +++ b/Models/FilteredLayer.ts @@ -1,14 +1,90 @@ import { UIEventSource } from "../Logic/UIEventSource" import LayerConfig from "./ThemeConfig/LayerConfig" -import { TagsFilter } from "../Logic/Tags/TagsFilter" +import { OsmConnection } from "../Logic/Osm/OsmConnection" +import { LocalStorageSource } from "../Logic/Web/LocalStorageSource" +import { QueryParameters } from "../Logic/Web/QueryParameters" -export interface FilterState { - currentFilter: TagsFilter - state: string | number -} - -export default interface FilteredLayer { +export default class FilteredLayer { + /** + * Wether or not the specified layer is shown + */ readonly isDisplayed: UIEventSource - readonly appliedFilters: UIEventSource> + /** + * Maps the filter.option.id onto the actual used state + */ + readonly appliedFilters: Map> readonly layerDef: LayerConfig + + constructor( + layer: LayerConfig, + appliedFilters?: Map>, + isDisplayed?: UIEventSource + ) { + this.layerDef = layer + this.isDisplayed = isDisplayed ?? new UIEventSource(true) + this.appliedFilters = + appliedFilters ?? new Map>() + } + + /** + * Creates a FilteredLayer which is tied into the QueryParameters and/or user preferences + */ + public static initLinkedState( + layer: LayerConfig, + context: string, + osmConnection: OsmConnection + ) { + let isDisplayed: UIEventSource + if (layer.syncSelection === "local") { + isDisplayed = LocalStorageSource.GetParsed( + context + "-layer-" + layer.id + "-enabled", + layer.shownByDefault + ) + } else if (layer.syncSelection === "theme-only") { + isDisplayed = FilteredLayer.getPref( + osmConnection, + context + "-layer-" + layer.id + "-enabled", + layer + ) + } else if (layer.syncSelection === "global") { + isDisplayed = FilteredLayer.getPref( + osmConnection, + "layer-" + layer.id + "-enabled", + layer + ) + } else { + isDisplayed = QueryParameters.GetBooleanQueryParameter( + "layer-" + layer.id, + layer.shownByDefault, + "Whether or not layer " + layer.id + " is shown" + ) + } + + const appliedFilters = new Map>() + for (const subfilter of layer.filters) { + appliedFilters.set(subfilter.id, subfilter.initState()) + } + return new FilteredLayer(layer, appliedFilters, isDisplayed) + } + private static getPref( + osmConnection: OsmConnection, + key: string, + layer: LayerConfig + ): UIEventSource { + return osmConnection.GetPreference(key, layer.shownByDefault + "").sync( + (v) => { + if (v === undefined) { + return undefined + } + return v === "true" + }, + [], + (b) => { + if (b === undefined) { + return undefined + } + return "" + b + } + ) + } } diff --git a/Models/GlobalFilter.ts b/Models/GlobalFilter.ts index c57a818a7d..91750c7bd7 100644 --- a/Models/GlobalFilter.ts +++ b/Models/GlobalFilter.ts @@ -1,9 +1,10 @@ import { Translation, TypedTranslation } from "../UI/i18n/Translation" -import { FilterState } from "./FilteredLayer" import { Tag } from "../Logic/Tags/Tag" +import { TagsFilter } from "../Logic/Tags/TagsFilter" export interface GlobalFilter { - filter: FilterState + osmTags: TagsFilter + state: number | string | undefined id: string onNewPoint: { safetyCheck: Translation diff --git a/Models/MapProperties.ts b/Models/MapProperties.ts index 39ac125c2f..cbb34e7f3d 100644 --- a/Models/MapProperties.ts +++ b/Models/MapProperties.ts @@ -5,8 +5,10 @@ import { RasterLayerPolygon } from "./RasterLayers" export interface MapProperties { readonly location: UIEventSource<{ lon: number; lat: number }> readonly zoom: UIEventSource - readonly bounds: Store + readonly bounds: UIEventSource readonly rasterLayer: UIEventSource readonly maxbounds: UIEventSource readonly allowMoving: UIEventSource + + readonly allowZooming: UIEventSource } diff --git a/Models/RasterLayers.ts b/Models/RasterLayers.ts index 84d9fc53e4..ec75de3c80 100644 --- a/Models/RasterLayers.ts +++ b/Models/RasterLayers.ts @@ -36,6 +36,14 @@ export class AvailableRasterLayers { geometry: BBox.global.asGeometry(), } + public static readonly maplibre: RasterLayerPolygon = { + type: "Feature", + properties: { + name: "MapLibre", + url: null, + }, + geometry: BBox.global.asGeometry(), + } public static layersAvailableAt( location: Store<{ lon: number; lat: number }> ): Store { @@ -58,6 +66,7 @@ export class AvailableRasterLayers { return GeoOperations.inside(lonlat, eliPolygon) }) matching.unshift(AvailableRasterLayers.osmCarto) + matching.unshift(AvailableRasterLayers.maplibre) matching.push(...AvailableRasterLayers.globalLayers) return matching }) diff --git a/Models/ThemeConfig/FilterConfig.ts b/Models/ThemeConfig/FilterConfig.ts index 4be92468d3..a7774fb2aa 100644 --- a/Models/ThemeConfig/FilterConfig.ts +++ b/Models/ThemeConfig/FilterConfig.ts @@ -5,7 +5,6 @@ import Translations from "../../UI/i18n/Translations" import { TagUtils } from "../../Logic/Tags/TagUtils" import { TagConfigJson } from "./Json/TagConfigJson" import { UIEventSource } from "../../Logic/UIEventSource" -import { FilterState } from "../FilteredLayer" import { QueryParameters } from "../../Logic/Web/QueryParameters" import { Utils } from "../../Utils" import { RegexTag } from "../../Logic/Tags/RegexTag" @@ -144,14 +143,7 @@ export default class FilterConfig { }) } - public initState(): UIEventSource { - function reset(state: FilterState): string { - if (state === undefined) { - return "" - } - return "" + state.state - } - + public initState(): UIEventSource { let defaultValue = "" if (this.options.length > 1) { defaultValue = "" + (this.defaultSelection ?? 0) @@ -159,6 +151,8 @@ export default class FilterConfig { // Only a single option if (this.defaultSelection === 0) { defaultValue = "true" + } else { + defaultValue = "false" } } const qp = QueryParameters.GetQueryParameter( @@ -168,12 +162,6 @@ export default class FilterConfig { ) if (this.options.length > 1) { - // This is a multi-option filter; state should be a number which selects the correct entry - const possibleStates: FilterState[] = this.options.map((opt, i) => ({ - currentFilter: opt.osmTags, - state: i, - })) - // We map the query parameter for this case return qp.sync( (str) => { @@ -182,62 +170,29 @@ export default class FilterConfig { // Nope, not a correct number! return undefined } - return possibleStates[parsed] + return parsed }, [], - reset + (n) => "" + n ) } const option = this.options[0] if (option.fields.length > 0) { - return qp.sync( - (str) => { - // There are variables in play! - // str should encode a json-hash - try { - const props = JSON.parse(str) - - const origTags = option.originalTagsSpec - const rewrittenTags = Utils.WalkJson(origTags, (v) => { - if (typeof v !== "string") { - return v - } - for (const key in props) { - v = (v).replace("{" + key + "}", props[key]) - } - return v - }) - const parsed = TagUtils.Tag(rewrittenTags) - return { - currentFilter: parsed, - state: str, - } - } catch (e) { - return undefined - } - }, - [], - reset - ) + return qp } - // The last case is pretty boring: it is checked or it isn't - const filterState: FilterState = { - currentFilter: option.osmTags, - state: "true", - } return qp.sync( (str) => { // Only a single option exists here if (str === "true") { - return filterState + return 0 } return undefined }, [], - reset + (n) => (n === undefined ? "false" : "true") ) } diff --git a/Models/ThemeConfig/Json/LayoutConfigJson.ts b/Models/ThemeConfig/Json/LayoutConfigJson.ts index 4a2a761b7c..33099a0626 100644 --- a/Models/ThemeConfig/Json/LayoutConfigJson.ts +++ b/Models/ThemeConfig/Json/LayoutConfigJson.ts @@ -205,25 +205,6 @@ export interface LayoutConfigJson { } )[] - /** - * If defined, data will be clustered. - * Defaults to {maxZoom: 16, minNeeded: 500} - */ - clustering?: - | { - /** - * All zoom levels above 'maxzoom' are not clustered anymore. - * Defaults to 18 - */ - maxZoom?: number - /** - * The number of elements per tile needed to start clustering - * If clustering is defined, defaults to 250 - */ - minNeededElements?: number - } - | false - /** * The URL of a custom CSS stylesheet to modify the layout */ diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts index 4068b4fe83..a852401202 100644 --- a/Models/ThemeConfig/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -40,10 +40,6 @@ export default class LayoutConfig implements LayoutInformation { public defaultBackgroundId?: string public layers: LayerConfig[] public tileLayerSources: TilesourceConfig[] - public readonly clustering?: { - maxZoom: number - minNeededElements: number - } public readonly hideFromOverview: boolean public lockLocation: boolean | [[number, number], [number, number]] public readonly enableUserBadge: boolean @@ -188,22 +184,6 @@ export default class LayoutConfig implements LayoutInformation { context + ".extraLink" ) - this.clustering = { - maxZoom: 16, - minNeededElements: 250, - } - if (json.clustering === false) { - this.clustering = { - maxZoom: 0, - minNeededElements: 100000, - } - } else if (json.clustering) { - this.clustering = { - maxZoom: json.clustering.maxZoom ?? 18, - minNeededElements: json.clustering.minNeededElements ?? 250, - } - } - this.hideFromOverview = json.hideFromOverview ?? false this.lockLocation = <[[number, number], [number, number]]>json.lockLocation ?? undefined this.enableUserBadge = json.enableUserBadge ?? true diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts index e8a467bf83..876ff425ce 100644 --- a/Models/ThemeConfig/PointRenderingConfig.ts +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -11,8 +11,6 @@ import { FixedUiElement } from "../../UI/Base/FixedUiElement" import Img from "../../UI/Base/Img" import Combine from "../../UI/Base/Combine" import { VariableUiElement } from "../../UI/Base/VariableUIElement" -import { OsmTags } from "../OsmFeature" -import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" export default class PointRenderingConfig extends WithContextLoader { private static readonly allowed_location_codes = new Set([ @@ -176,7 +174,7 @@ export default class PointRenderingConfig extends WithContextLoader { return PointRenderingConfig.FromHtmlMulti(htmlDefs, rotation, false, defaultPin) } - public GetSimpleIcon(tags: Store): BaseUIElement { + public GetSimpleIcon(tags: Store>): BaseUIElement { const self = this if (this.icon === undefined) { return undefined @@ -187,7 +185,7 @@ export default class PointRenderingConfig extends WithContextLoader { } public RenderIcon( - tags: Store, + tags: Store>, clickable: boolean, options?: { noSize?: false | boolean @@ -277,7 +275,7 @@ export default class PointRenderingConfig extends WithContextLoader { } } - private GetBadges(tags: Store): BaseUIElement { + private GetBadges(tags: Store>): BaseUIElement { if (this.iconBadges.length === 0) { return undefined } @@ -309,7 +307,7 @@ export default class PointRenderingConfig extends WithContextLoader { ).SetClass("absolute bottom-0 right-1/3 h-1/2 w-0") } - private GetLabel(tags: Store): BaseUIElement { + private GetLabel(tags: Store>): BaseUIElement { if (this.label === undefined) { return undefined } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts new file mode 100644 index 0000000000..f2f36e34f2 --- /dev/null +++ b/Models/ThemeViewState.ts @@ -0,0 +1,278 @@ +import LayoutConfig from "./ThemeConfig/LayoutConfig" +import { SpecialVisualizationState } from "../UI/SpecialVisualization" +import { Changes } from "../Logic/Osm/Changes" +import { Store, UIEventSource } from "../Logic/UIEventSource" +import FeatureSource, { + IndexedFeatureSource, + WritableFeatureSource, +} from "../Logic/FeatureSource/FeatureSource" +import { OsmConnection } from "../Logic/Osm/OsmConnection" +import { DefaultGuiState } from "../UI/DefaultGuiState" +import { MapProperties } from "./MapProperties" +import LayerState from "../Logic/State/LayerState" +import { Feature } from "geojson" +import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" +import { Map as MlMap } from "maplibre-gl" +import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning" +import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor" +import { GeoLocationState } from "../Logic/State/GeoLocationState" +import FeatureSwitchState from "../Logic/State/FeatureSwitchState" +import { QueryParameters } from "../Logic/Web/QueryParameters" +import UserRelatedState from "../Logic/State/UserRelatedState" +import LayerConfig from "./ThemeConfig/LayerConfig" +import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" +import { AvailableRasterLayers, RasterLayerPolygon } from "./RasterLayers" +import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource" +import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" +import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore" +import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter" +import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" +import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource" +import ShowDataLayer from "../UI/Map/ShowDataLayer" +import TitleHandler from "../Logic/Actors/TitleHandler" +import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor" +import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader" +import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater" +import { BBox } from "../Logic/BBox" +import Constants from "./Constants" +import Hotkeys from "../UI/Base/Hotkeys" +import Translations from "../UI/i18n/Translations" +import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" + +/** + * + * The themeviewState contains all the state needed for the themeViewGUI. + * + * This is pretty much the 'brain' or the HQ of MapComplete + * + * It ties up all the needed elements and starts some actors. + */ +export default class ThemeViewState implements SpecialVisualizationState { + readonly layout: LayoutConfig + readonly map: UIEventSource + readonly changes: Changes + readonly featureSwitches: FeatureSwitchState + readonly featureSwitchIsTesting: Store + readonly featureSwitchUserbadge: Store + + readonly featureProperties: FeaturePropertiesStore + + readonly osmConnection: OsmConnection + readonly selectedElement: UIEventSource + readonly mapProperties: MapProperties + + readonly dataIsLoading: Store // TODO + readonly guistate: DefaultGuiState + readonly fullNodeDatabase?: FullNodeDatabaseSource // TODO + + readonly historicalUserLocations: WritableFeatureSource + readonly indexedFeatures: IndexedFeatureSource + readonly layerState: LayerState + readonly perLayer: ReadonlyMap + readonly availableLayers: Store + readonly selectedLayer: UIEventSource + readonly userRelatedState: UserRelatedState + readonly geolocation: GeoLocationHandler + + constructor(layout: LayoutConfig) { + this.layout = layout + this.guistate = new DefaultGuiState() + this.map = new UIEventSource(undefined) + const initial = new InitialMapPositioning(layout) + this.mapProperties = new MapLibreAdaptor(this.map, initial) + const geolocationState = new GeoLocationState() + + this.featureSwitches = new FeatureSwitchState(layout) + this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting + this.featureSwitchUserbadge = this.featureSwitches.featureSwitchUserbadge + + this.osmConnection = new OsmConnection({ + dryRun: this.featureSwitches.featureSwitchIsTesting, + fakeUser: this.featureSwitches.featureSwitchFakeUser.data, + oauth_token: QueryParameters.GetQueryParameter( + "oauth_token", + undefined, + "Used to complete the login" + ), + osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data, + }) + this.userRelatedState = new UserRelatedState(this.osmConnection, layout?.language) + this.selectedElement = new UIEventSource(undefined, "Selected element") + this.selectedLayer = new UIEventSource(undefined, "Selected layer") + this.geolocation = new GeoLocationHandler( + geolocationState, + this.selectedElement, + this.mapProperties, + this.userRelatedState.gpsLocationHistoryRetentionTime + ) + this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location) + + this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id) + const indexedElements = new LayoutSource( + layout.layers, + this.featureSwitches, + new StaticFeatureSource([]), + this.mapProperties, + this.osmConnection.Backend(), + (id) => this.layerState.filteredLayers.get(id).isDisplayed + ) + this.featureProperties = new FeaturePropertiesStore(indexedElements) + const perLayer = new PerLayerFeatureSourceSplitter( + Array.from(this.layerState.filteredLayers.values()), + indexedElements, + { + constructStore: (features, layer) => new GeoIndexedStoreForLayer(features, layer), + } + ) + this.perLayer = perLayer.perLayer + + this.perLayer.forEach((fs) => { + new SaveFeatureSourceToLocalStorage(fs.layer.layerDef.id, 15, fs) + + const filtered = new FilteringFeatureSource( + fs.layer, + fs, + (id) => this.featureProperties.getStore(id), + this.layerState.globalFilters + ) + const doShowLayer = this.mapProperties.zoom.map( + (z) => + (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), + [fs.layer.isDisplayed] + ) + doShowLayer.addCallbackAndRunD((doShow) => + console.log( + "Layer", + fs.layer.layerDef.id, + "is", + doShow, + this.mapProperties.zoom.data, + fs.layer.layerDef.minzoom + ) + ) + new ShowDataLayer(this.map, { + layer: fs.layer.layerDef, + features: filtered, + doShowLayer, + selectedElement: this.selectedElement, + selectedLayer: this.selectedLayer, + fetchStore: (id) => this.featureProperties.getStore(id), + }) + }) + + this.changes = new Changes( + { + dryRun: this.featureSwitches.featureSwitchIsTesting, + allElements: indexedElements, + featurePropertiesStore: this.featureProperties, + osmConnection: this.osmConnection, + historicalUserLocations: this.geolocation.historicalUserLocations, + }, + layout?.isLeftRightSensitive() ?? false + ) + + this.initActors() + this.drawSpecialLayers() + this.initHotkeys() + this.miscSetup() + } + + /** + * Various small methods that need to be called + */ + private miscSetup() { + this.userRelatedState.markLayoutAsVisited(this.layout) + } + + private initHotkeys() { + Hotkeys.RegisterHotkey( + { nomod: "Escape", onUp: true }, + Translations.t.hotkeyDocumentation.closeSidebar, + () => { + this.selectedElement.setData(undefined) + this.guistate.closeAll() + } + ) + } + + /** + * Add the special layers to the map + * @private + */ + private drawSpecialLayers() { + type AddedByDefaultTypes = typeof Constants.added_by_default[number] + /** + * A listing which maps the layerId onto the featureSource + */ + const empty = [] + const specialLayers: Record = { + home_location: this.userRelatedState.homeLocation, + gps_location: this.geolocation.currentUserLocation, + gps_location_history: this.geolocation.historicalUserLocations, + gps_track: this.geolocation.historicalUserLocationsTrack, + selected_element: new StaticFeatureSource( + this.selectedElement.map((f) => (f === undefined ? empty : [f])) + ), + range: new StaticFeatureSource( + this.mapProperties.maxbounds.map((bbox) => + bbox === undefined ? empty : [bbox.asGeoJson({ id: "range" })] + ) + ), + current_view: new StaticFeatureSource( + this.mapProperties.bounds.map((bbox) => + bbox === undefined ? empty : [bbox.asGeoJson({ id: "current_view" })] + ) + ), + } + if (this.layout?.lockLocation) { + const bbox = new BBox(this.layout.lockLocation) + this.mapProperties.maxbounds.setData(bbox) + ShowDataLayer.showRange( + this.map, + new StaticFeatureSource([bbox.asGeoJson({})]), + this.featureSwitches.featureSwitchIsTesting + ) + } + + this.layerState.filteredLayers + .get("range") + ?.isDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true) + + this.layerState.filteredLayers.forEach((flayer) => { + const features = specialLayers[flayer.layerDef.id] + if (features === undefined) { + return + } + new ShowDataLayer(this.map, { + features, + doShowLayer: flayer.isDisplayed, + layer: flayer.layerDef, + selectedElement: this.selectedElement, + selectedLayer: this.selectedLayer, + }) + }) + } + + /** + * Setup various services for which no reference are needed + * @private + */ + private initActors() { + // Various actors that we don't need to reference + new TitleHandler( + this.selectedElement, + this.selectedLayer, + this.featureProperties, + this.layout + ) + new ChangeToElementsActor(this.changes, this.featureProperties) + new PendingChangesUploader(this.changes, this.selectedElement) + new SelectedElementTagsUpdater({ + allElements: this.featureProperties, + changes: this.changes, + selectedElement: this.selectedElement, + layoutToUse: this.layout, + osmConnection: this.osmConnection, + }) + } +} diff --git a/State.ts b/State.ts deleted file mode 100644 index 2572dc84a3..0000000000 --- a/State.ts +++ /dev/null @@ -1,16 +0,0 @@ -import LayoutConfig from "./Models/ThemeConfig/LayoutConfig" -import FeaturePipelineState from "./Logic/State/FeaturePipelineState" - -/** - * Contains the global state: a bunch of UI-event sources - */ - -export default class State extends FeaturePipelineState { - /* The singleton of the global state - */ - public static state: FeaturePipelineState - - constructor(layoutToUse: LayoutConfig) { - super(layoutToUse) - } -} diff --git a/UI/Base/Checkbox.svelte b/UI/Base/Checkbox.svelte new file mode 100644 index 0000000000..93fec94832 --- /dev/null +++ b/UI/Base/Checkbox.svelte @@ -0,0 +1,13 @@ + + + diff --git a/UI/Base/Dropdown.svelte b/UI/Base/Dropdown.svelte new file mode 100644 index 0000000000..2e711d954b --- /dev/null +++ b/UI/Base/Dropdown.svelte @@ -0,0 +1,15 @@ + + + diff --git a/UI/Base/If.svelte b/UI/Base/If.svelte index 124ff330bf..5255b8046e 100644 --- a/UI/Base/If.svelte +++ b/UI/Base/If.svelte @@ -1,14 +1,23 @@ {#if _c} + {:else} + {/if} diff --git a/UI/Base/IfNot.svelte b/UI/Base/IfNot.svelte new file mode 100644 index 0000000000..baab7c1900 --- /dev/null +++ b/UI/Base/IfNot.svelte @@ -0,0 +1,18 @@ + + +{#if _c} + +{/if} diff --git a/UI/Base/Loading.svelte b/UI/Base/Loading.svelte new file mode 100644 index 0000000000..2db2e44818 --- /dev/null +++ b/UI/Base/Loading.svelte @@ -0,0 +1,13 @@ + + +
+
+ +
+
+ +
+
diff --git a/UI/Base/MapControlButton.svelte b/UI/Base/MapControlButton.svelte index fd87d3d244..9d875b9840 100644 --- a/UI/Base/MapControlButton.svelte +++ b/UI/Base/MapControlButton.svelte @@ -8,6 +8,6 @@ -
dispatch("click", e)} class="subtle-background block rounded-full min-w-10 h-10 pointer-events-auto m-0.5 md:m-1 p-1"> +
dispatch("click", e)} class="subtle-background rounded-full min-w-10 w-fit h-10 m-0.5 md:m-1 p-1">
diff --git a/UI/Base/ToSvelte.svelte b/UI/Base/ToSvelte.svelte index 69dfe51f86..7edaddf1bc 100644 --- a/UI/Base/ToSvelte.svelte +++ b/UI/Base/ToSvelte.svelte @@ -1,18 +1,23 @@ diff --git a/UI/BigComponents/ActionButtons.ts b/UI/BigComponents/ActionButtons.ts index 1946ea8932..fad2a02240 100644 --- a/UI/BigComponents/ActionButtons.ts +++ b/UI/BigComponents/ActionButtons.ts @@ -13,6 +13,7 @@ import { OpenIdEditor, OpenJosm } from "./CopyrightPanel" import Toggle from "../Input/Toggle" import ScrollableFullScreen from "../Base/ScrollableFullScreen" import { DefaultGuiState } from "../DefaultGuiState" +import DefaultGUI from "../DefaultGUI" export class BackToThemeOverview extends Toggle { constructor( @@ -42,6 +43,7 @@ export class ActionButtons extends Combine { readonly locationControl: Store readonly osmConnection: OsmConnection readonly featureSwitchMoreQuests: Store + readonly defaultGuiState: DefaultGuiState }) { const imgSize = "h-6 w-6" const iconStyle = "height: 1.5rem; width: 1.5rem" @@ -82,8 +84,8 @@ export class ActionButtons extends Combine { Translations.t.translations.activateButton ).onClick(() => { ScrollableFullScreen.collapse() - DefaultGuiState.state.userInfoIsOpened.setData(true) - DefaultGuiState.state.userInfoFocusedQuestion.setData("translation-mode") + state.defaultGuiState.userInfoIsOpened.setData(true) + state.defaultGuiState.userInfoFocusedQuestion.setData("translation-mode") }), ]) this.SetClass("block w-full link-no-underline") diff --git a/UI/BigComponents/CopyrightPanel.ts b/UI/BigComponents/CopyrightPanel.ts index b9f0c5c758..0a7d2bb845 100644 --- a/UI/BigComponents/CopyrightPanel.ts +++ b/UI/BigComponents/CopyrightPanel.ts @@ -14,54 +14,53 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import Title from "../Base/Title" import { SubtleButton } from "../Base/SubtleButton" import Svg from "../../Svg" -import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" import { BBox } from "../../Logic/BBox" -import Loc from "../../Models/Loc" import Toggle from "../Input/Toggle" import { OsmConnection } from "../../Logic/Osm/OsmConnection" import Constants from "../../Models/Constants" import ContributorCount from "../../Logic/ContributorCount" import Img from "../Base/Img" import { TypedTranslation } from "../i18n/Translation" +import GeoIndexedStore from "../../Logic/FeatureSource/Actors/GeoIndexedStore" export class OpenIdEditor extends VariableUiElement { constructor( - state: { readonly locationControl: Store }, + mapProperties: { location: Store<{ lon: number; lat: number }>; zoom: Store }, iconStyle?: string, objectId?: string ) { const t = Translations.t.general.attribution super( - state.locationControl.map((location) => { - let elementSelect = "" - if (objectId !== undefined) { - const parts = objectId.split("/") - const tp = parts[0] - if ( - parts.length === 2 && - !isNaN(Number(parts[1])) && - (tp === "node" || tp === "way" || tp === "relation") - ) { - elementSelect = "&" + tp + "=" + parts[1] + mapProperties.location.map( + (location) => { + let elementSelect = "" + if (objectId !== undefined) { + const parts = objectId.split("/") + const tp = parts[0] + if ( + parts.length === 2 && + !isNaN(Number(parts[1])) && + (tp === "node" || tp === "way" || tp === "relation") + ) { + elementSelect = "&" + tp + "=" + parts[1] + } } - } - const idLink = `https://www.openstreetmap.org/edit?editor=id${elementSelect}#map=${ - location?.zoom ?? 0 - }/${location?.lat ?? 0}/${location?.lon ?? 0}` - return new SubtleButton(Svg.pencil_ui().SetStyle(iconStyle), t.editId, { - url: idLink, - newTab: true, - }) - }) + const idLink = `https://www.openstreetmap.org/edit?editor=id${elementSelect}#map=${ + mapProperties.zoom?.data ?? 0 + }/${location?.lat ?? 0}/${location?.lon ?? 0}` + return new SubtleButton(Svg.pencil_ui().SetStyle(iconStyle), t.editId, { + url: idLink, + newTab: true, + }) + }, + [mapProperties.zoom] + ) ) } } export class OpenJosm extends Combine { - constructor( - state: { osmConnection: OsmConnection; currentBounds: Store }, - iconStyle?: string - ) { + constructor(osmConnection: OsmConnection, bounds: Store, iconStyle?: string) { const t = Translations.t.general.attribution const josmState = new UIEventSource(undefined) @@ -83,21 +82,21 @@ export class OpenJosm extends Combine { const toggle = new Toggle( new SubtleButton(Svg.josm_logo_ui().SetStyle(iconStyle), t.editJosm).onClick(() => { - const bounds: any = state.currentBounds.data - if (bounds === undefined) { - return undefined + const bbox = bounds.data + if (bbox === undefined) { + return } - const top = bounds.getNorth() - const bottom = bounds.getSouth() - const right = bounds.getEast() - const left = bounds.getWest() + const top = bbox.getNorth() + const bottom = bbox.getSouth() + const right = bbox.getEast() + const left = bbox.getWest() const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}` Utils.download(josmLink) .then((answer) => josmState.setData(answer.replace(/\n/g, "").trim())) .catch((_) => josmState.setData("ERROR")) }), undefined, - state.osmConnection.userDetails.map( + osmConnection.userDetails.map( (ud) => ud.loggedIn && ud.csCount >= Constants.userJourney.historyLinkVisible ) ) @@ -113,14 +112,14 @@ export default class CopyrightPanel extends Combine { private static LicenseObject = CopyrightPanel.GenerateLicenses() constructor(state: { - layoutToUse: LayoutConfig - featurePipeline: FeaturePipeline - currentBounds: Store - locationControl: UIEventSource + layout: LayoutConfig + bounds: Store osmConnection: OsmConnection + dataIsLoading: Store + perLayer: ReadonlyMap }) { const t = Translations.t.general.attribution - const layoutToUse = state.layoutToUse + const layoutToUse = state.layout const iconAttributions: BaseUIElement[] = layoutToUse.usedImages.map( CopyrightPanel.IconAttribution diff --git a/UI/BigComponents/DownloadPanel.ts b/UI/BigComponents/DownloadPanel.ts index d07f95d255..98ffad07e4 100644 --- a/UI/BigComponents/DownloadPanel.ts +++ b/UI/BigComponents/DownloadPanel.ts @@ -1,7 +1,6 @@ import { SubtleButton } from "../Base/SubtleButton" import Svg from "../../Svg" import Translations from "../i18n/Translations" -import State from "../../State" import { Utils } from "../../Utils" import Combine from "../Base/Combine" import CheckBoxes from "../Input/Checkboxes" diff --git a/UI/BigComponents/FilterView.ts b/UI/BigComponents/FilterView.ts index e7bdbd59a7..c81c027488 100644 --- a/UI/BigComponents/FilterView.ts +++ b/UI/BigComponents/FilterView.ts @@ -1,16 +1,13 @@ import { Utils } from "../../Utils" -import { FixedInputElement } from "../Input/FixedInputElement" -import { RadioButton } from "../Input/RadioButton" import { VariableUiElement } from "../Base/VariableUIElement" -import Toggle, { ClickableToggle } from "../Input/Toggle" +import Toggle from "../Input/Toggle" import Combine from "../Base/Combine" import Translations from "../i18n/Translations" import { Translation } from "../i18n/Translation" import Svg from "../../Svg" import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" import BaseUIElement from "../BaseUIElement" -import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" -import BackgroundSelector from "./BackgroundSelector" +import FilteredLayer from "../../Models/FilteredLayer" import FilterConfig from "../../Models/ThemeConfig/FilterConfig" import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" import { SubstitutedTranslation } from "../SubstitutedTranslation" @@ -18,9 +15,7 @@ import ValidatedTextField from "../Input/ValidatedTextField" import { QueryParameters } from "../../Logic/Web/QueryParameters" import { TagUtils } from "../../Logic/Tags/TagUtils" import { InputElement } from "../Input/InputElement" -import { DropDown } from "../Input/DropDown" import { FixedUiElement } from "../Base/FixedUiElement" -import BaseLayer from "../../Models/BaseLayer" import Loc from "../../Models/Loc" import { BackToThemeOverview } from "./ActionButtons" @@ -272,102 +267,6 @@ export class LayerFilterPanel extends Combine { return [tr, settableFilter] } - private static createCheckboxFilter( - filterConfig: FilterConfig - ): [BaseUIElement, UIEventSource] { - let option = filterConfig.options[0] - - const icon = Svg.checkbox_filled_svg().SetClass("block mr-2 w-6") - const iconUnselected = Svg.checkbox_empty_svg().SetClass("block mr-2 w-6") - const qp = QueryParameters.GetBooleanQueryParameter( - "filter-" + filterConfig.id, - false, - "Is filter '" + filterConfig.options[0].question.textFor("en") + " enabled?" - ) - const toggle = new ClickableToggle( - new Combine([icon, option.question.Clone().SetClass("block")]).SetClass("flex"), - new Combine([iconUnselected, option.question.Clone().SetClass("block")]).SetClass( - "flex" - ), - qp - ) - .ToggleOnClick() - .SetClass("block m-1") - - return [ - toggle, - toggle.isEnabled.sync( - (enabled) => - enabled - ? { - currentFilter: option.osmTags, - state: "true", - } - : undefined, - [], - (f) => f !== undefined - ), - ] - } - - private static createMultiFilter( - filterConfig: FilterConfig - ): [BaseUIElement, UIEventSource] { - let options = filterConfig.options - - const values: FilterState[] = options.map((f, i) => ({ - currentFilter: f.osmTags, - state: i, - })) - let filterPicker: InputElement - const value = QueryParameters.GetQueryParameter( - "filter-" + filterConfig.id, - "0", - "Value for filter " + filterConfig.id - ).sync( - (str) => Number(str), - [], - (n) => "" + n - ) - - if (options.length <= 6) { - filterPicker = new RadioButton( - options.map( - (option, i) => - new FixedInputElement(option.question.Clone().SetClass("block"), i) - ), - { - value, - dontStyle: true, - } - ) - } else { - filterPicker = new DropDown( - "", - options.map((option, i) => ({ - value: i, - shown: option.question.Clone(), - })), - value - ) - } - - return [ - filterPicker, - filterPicker.GetValue().sync( - (i) => values[i], - [], - (selected) => { - const v = selected?.state - if (v === undefined || typeof v === "string") { - return undefined - } - return v - } - ), - ] - } - private static createFilter( state: {}, filterConfig: FilterConfig @@ -376,12 +275,6 @@ export class LayerFilterPanel extends Combine { return LayerFilterPanel.createFilterWithFields(state, filterConfig) } - if (filterConfig.options.length === 1) { - return LayerFilterPanel.createCheckboxFilter(filterConfig) - } - - const filter = LayerFilterPanel.createMultiFilter(filterConfig) - filter[0].SetClass("pl-2") - return filter + return undefined } } diff --git a/UI/BigComponents/Filterview.svelte b/UI/BigComponents/Filterview.svelte new file mode 100644 index 0000000000..f1e0c3508f --- /dev/null +++ b/UI/BigComponents/Filterview.svelte @@ -0,0 +1,79 @@ + +{#if filteredLayer.layerDef.name} +
+ + +
+ {#each filteredLayer.layerDef.filters as filter} +
+ + + {#if filter.options.length === 1 && filter.options[0].fields.length === 0} + + {/if} + + {#if filter.options.length > 1} + + {#each filter.options as option, i} + + {/each} + + {/if} + + +
+ {/each} +
+
+ +
+{/if} diff --git a/UI/BigComponents/GeolocationControl.ts b/UI/BigComponents/GeolocationControl.ts index f1aea540c6..3ebfd42d20 100644 --- a/UI/BigComponents/GeolocationControl.ts +++ b/UI/BigComponents/GeolocationControl.ts @@ -1,8 +1,7 @@ import { VariableUiElement } from "../Base/VariableUIElement" import Svg from "../../Svg" -import { Store, UIEventSource } from "../../Logic/UIEventSource" +import { UIEventSource } from "../../Logic/UIEventSource" import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler" -import { BBox } from "../../Logic/BBox" import Hotkeys from "../Base/Hotkeys" import Translations from "../i18n/Translations" import Constants from "../../Models/Constants" @@ -94,14 +93,13 @@ export class GeolocationControl extends VariableUiElement { return } - if (geolocationState.currentGPSLocation.data === undefined) { + // A location _is_ known! Let's move to this location + const currentLocation = geolocationState.currentGPSLocation.data + if (currentLocation === undefined) { // No location is known yet, not much we can do lastClick.setData(new Date()) return } - - // A location _is_ known! Let's move to this location - const currentLocation = geolocationState.currentGPSLocation.data const inBounds = state.bounds.data.contains([ currentLocation.longitude, currentLocation.latitude, diff --git a/UI/BigComponents/Geosearch.svelte b/UI/BigComponents/Geosearch.svelte new file mode 100644 index 0000000000..fe40cb7c73 --- /dev/null +++ b/UI/BigComponents/Geosearch.svelte @@ -0,0 +1,94 @@ + + +
+
+ + {#if isRunning} + {Translations.t.general.search.searching} + {:else if feedback !== undefined} +
feedback = undefined}> + {feedback} +
+ {:else } + keypr.key === "Enter" ? performSearch() : undefined} + + bind:value={searchContents} + placeholder={Translations.t.general.search.search}> + {/if} + +
+
+ +
+
diff --git a/UI/BigComponents/RightControls.ts b/UI/BigComponents/RightControls.ts index c34daa4797..7c5280abac 100644 --- a/UI/BigComponents/RightControls.ts +++ b/UI/BigComponents/RightControls.ts @@ -1,12 +1,6 @@ import Combine from "../Base/Combine" -import Toggle from "../Input/Toggle" -import MapControlButton from "../MapControlButton" -import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler" -import Svg from "../../Svg" import MapState from "../../Logic/State/MapState" -import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" import LevelSelector from "./LevelSelector" -import { GeolocationControl } from "./GeolocationControl" export default class RightControls extends Combine { constructor(state: MapState & { featurePipeline: FeaturePipeline }) { diff --git a/UI/BigComponents/SelectedElementView.svelte b/UI/BigComponents/SelectedElementView.svelte new file mode 100644 index 0000000000..5c6715326f --- /dev/null +++ b/UI/BigComponents/SelectedElementView.svelte @@ -0,0 +1,75 @@ + + +
+
selectedElement.setData(undefined)}>close
+
+ + new VariableUiElement(tags.mapD(tags => new TagRenderingAnswer(tags, layer.data.title, specialVisState), [layer]))}> + +
+ + {#each $layer.titleIcons as titleIconConfig (titleIconConfig.id)} +
+ new VariableUiElement(tags.mapD(tags => new TagRenderingAnswer(tags, titleIconConfig, specialVisState)))}> +
+ + {/each} +
+ + +
+ +
    + + {#each Object.keys($_tags) as key} +
  • {key}={$_tags[key]}
  • + {/each} +
+
diff --git a/UI/BigComponents/StatisticsPanel.ts b/UI/BigComponents/StatisticsPanel.ts index ed8dbab495..2bb479b7bd 100644 --- a/UI/BigComponents/StatisticsPanel.ts +++ b/UI/BigComponents/StatisticsPanel.ts @@ -4,20 +4,14 @@ import Title from "../Base/Title" import TagRenderingChart from "./TagRenderingChart" import Combine from "../Base/Combine" import Locale from "../i18n/Locale" -import { UIEventSource } from "../../Logic/UIEventSource" -import { OsmFeature } from "../../Models/OsmFeature" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" +import { FeatureSourceForLayer } from "../../Logic/FeatureSource/FeatureSource" +import BaseUIElement from "../BaseUIElement" -export default class StatisticsPanel extends VariableUiElement { - constructor( - elementsInview: UIEventSource<{ element: OsmFeature; layer: LayerConfig }[]>, - state: { - layoutToUse: LayoutConfig - } - ) { +export default class StatisticsForLayerPanel extends VariableUiElement { + constructor(elementsInview: FeatureSourceForLayer) { + const layer = elementsInview.layer.layerDef super( - elementsInview.stabilized(1000).map( + elementsInview.features.stabilized(1000).map( (features) => { if (features === undefined) { return new Loading("Loading data") @@ -25,40 +19,33 @@ export default class StatisticsPanel extends VariableUiElement { if (features.length === 0) { return "No elements in view" } - const els = [] - for (const layer of state.layoutToUse.layers) { - if (layer.name === undefined) { - continue - } - const featuresForLayer = features - .filter((f) => f.layer === layer) - .map((f) => f.element) - if (featuresForLayer.length === 0) { - continue - } - els.push(new Title(layer.name.Clone(), 1).SetClass("mt-8")) - - const layerStats = [] - for (const tagRendering of layer?.tagRenderings ?? []) { - const chart = new TagRenderingChart(featuresForLayer, tagRendering, { - chartclasses: "w-full", - chartstyle: "height: 60rem", - includeTitle: false, - }) - const title = new Title( - tagRendering.question?.Clone() ?? tagRendering.id, - 4 - ).SetClass("mt-8") - if (!chart.HasClass("hidden")) { - layerStats.push( - new Combine([title, chart]).SetClass( - "flex flex-col w-full lg:w-1/3" - ) - ) - } - } - els.push(new Combine(layerStats).SetClass("flex flex-wrap")) + const els: BaseUIElement[] = [] + const featuresForLayer = features + if (featuresForLayer.length === 0) { + return } + els.push(new Title(layer.name.Clone(), 1).SetClass("mt-8")) + + const layerStats = [] + for (const tagRendering of layer?.tagRenderings ?? []) { + const chart = new TagRenderingChart(featuresForLayer, tagRendering, { + chartclasses: "w-full", + chartstyle: "height: 60rem", + includeTitle: false, + }) + const title = new Title( + tagRendering.question?.Clone() ?? tagRendering.id, + 4 + ).SetClass("mt-8") + if (!chart.HasClass("hidden")) { + layerStats.push( + new Combine([title, chart]).SetClass( + "flex flex-col w-full lg:w-1/3" + ) + ) + } + } + els.push(new Combine(layerStats).SetClass("flex flex-wrap")) return new Combine(els) }, [Locale.language] diff --git a/UI/BigComponents/ThemeIntroductionPanel.ts b/UI/BigComponents/ThemeIntroductionPanel.ts index 02dc660c66..587cc0be43 100644 --- a/UI/BigComponents/ThemeIntroductionPanel.ts +++ b/UI/BigComponents/ThemeIntroductionPanel.ts @@ -11,6 +11,7 @@ import LoggedInUserIndicator from "../LoggedInUserIndicator" import { ActionButtons } from "./ActionButtons" import { BBox } from "../../Logic/BBox" import Loc from "../../Models/Loc" +import { DefaultGuiState } from "../DefaultGuiState" export default class ThemeIntroductionPanel extends Combine { constructor( @@ -24,6 +25,7 @@ export default class ThemeIntroductionPanel extends Combine { osmConnection: OsmConnection currentBounds: Store locationControl: UIEventSource + defaultGuiState: DefaultGuiState }, guistate?: { userInfoIsOpened: UIEventSource } ) { diff --git a/UI/BigComponents/UploadTraceToOsmUI.ts b/UI/BigComponents/UploadTraceToOsmUI.ts index 290a6b3db3..3d7c43910a 100644 --- a/UI/BigComponents/UploadTraceToOsmUI.ts +++ b/UI/BigComponents/UploadTraceToOsmUI.ts @@ -17,7 +17,7 @@ export default class UploadTraceToOsmUI extends LoginToggle { constructor( trace: (title: string) => string, state: { - layoutToUse: LayoutConfig + layout: LayoutConfig osmConnection: OsmConnection readonly featureSwitchUserbadge: Store }, diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index c793c7a42b..af3cc05dc0 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -1,5 +1,4 @@ import FeaturePipelineState from "../Logic/State/FeaturePipelineState" -import State from "../State" import { Utils } from "../Utils" import { UIEventSource } from "../Logic/UIEventSource" import FullWelcomePaneWithTabs from "./BigComponents/FullWelcomePaneWithTabs" @@ -11,14 +10,11 @@ import BaseUIElement from "./BaseUIElement" import LeftControls from "./BigComponents/LeftControls" import RightControls from "./BigComponents/RightControls" import CenterMessageBox from "./CenterMessageBox" -import ShowDataLayer from "./ShowDataLayer/ShowDataLayer" import ScrollableFullScreen from "./Base/ScrollableFullScreen" import Translations from "./i18n/Translations" import SimpleAddUI from "./BigComponents/SimpleAddUI" import StrayClickHandler from "../Logic/Actors/StrayClickHandler" import { DefaultGuiState } from "./DefaultGuiState" -import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import home_location_json from "../assets/layers/home_location/home_location.json" import NewNoteUi from "./Popup/NewNoteUi" import Combine from "./Base/Combine" import AddNewMarker from "./BigComponents/AddNewMarker" @@ -32,7 +28,6 @@ import { FixedUiElement } from "./Base/FixedUiElement" import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" import { GeoLocationState } from "../Logic/State/GeoLocationState" import Hotkeys from "./Base/Hotkeys" -import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers" import CopyrightPanel from "./BigComponents/CopyrightPanel" import SvelteUIElement from "./Base/SvelteUIElement" import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte" @@ -50,9 +45,6 @@ export default class DefaultGUI { constructor(state: FeaturePipelineState, guiState: DefaultGuiState) { this.state = state this.guiState = guiState - if (this.state.featureSwitchGeolocation.data) { - this.geolocationHandler = new GeoLocationHandler(new GeoLocationState(), state) - } } public setup() { @@ -74,10 +66,6 @@ export default class DefaultGUI { this.state.backgroundLayer.setData(AvailableBaseLayers.osmCarto) } ) - - Utils.downloadJson("./service-worker-version") - .then((data) => console.log("Service worker", data)) - .catch((_) => console.log("Service worker not active")) } public setupClickDialogOnMap( @@ -173,13 +161,6 @@ export default class DefaultGUI { this.setupClickDialogOnMap(guiState.filterViewIsOpened, state) - new ShowDataLayer({ - leafletMap: state.leafletMap, - layerToShow: new LayerConfig(home_location_json, "home_location", true), - features: state.homeLocation, - state, - }) - const selectedElement: FilteredLayer = state.filteredLayers.data.filter( (l) => l.layerDef.id === "selected_element" )[0] @@ -285,23 +266,6 @@ export default class DefaultGUI { .SetClass("flex items-center justify-center normal-background h-full") .AttachTo("on-small-screen") - new Combine([ - Toggle.If(state.featureSwitchSearch, () => { - const search = new SearchAndGo(state).SetClass( - "shadow rounded-full h-min w-full overflow-hidden sm:max-w-sm pointer-events-auto" - ) - Hotkeys.RegisterHotkey( - { ctrl: "F" }, - Translations.t.hotkeyDocumentation.selectSearch, - () => { - search.focus() - } - ) - - return search - }), - ]).AttachTo("top-right") - new LeftControls(state, guiState).AttachTo("bottom-left") new RightControls(state, this.geolocationHandler).AttachTo("bottom-right") diff --git a/UI/DefaultGuiState.ts b/UI/DefaultGuiState.ts index e56870e1ac..48b9ad71f5 100644 --- a/UI/DefaultGuiState.ts +++ b/UI/DefaultGuiState.ts @@ -1,13 +1,13 @@ import { UIEventSource } from "../Logic/UIEventSource" -import { QueryParameters } from "../Logic/Web/QueryParameters" import Hash from "../Logic/Web/Hash" export class DefaultGuiState { - static state: DefaultGuiState - public readonly welcomeMessageIsOpened: UIEventSource = new UIEventSource( false ) + + public readonly menuIsOpened: UIEventSource = new UIEventSource(false) + public readonly downloadControlIsOpened: UIEventSource = new UIEventSource( false ) @@ -22,25 +22,17 @@ export class DefaultGuiState { public readonly userInfoFocusedQuestion: UIEventSource = new UIEventSource( undefined ) - public readonly welcomeMessageOpenedTab: UIEventSource + + private readonly sources: Record> = { + welcome: this.welcomeMessageIsOpened, + download: this.downloadControlIsOpened, + filters: this.filterViewIsOpened, + copyright: this.copyrightViewIsOpened, + currentview: this.currentViewControlIsOpened, + userinfo: this.userInfoIsOpened, + } constructor() { - this.welcomeMessageOpenedTab = UIEventSource.asFloat( - QueryParameters.GetQueryParameter( - "tab", - "0", - `The tab that is shown in the welcome-message.` - ) - ) - const sources = { - welcome: this.welcomeMessageIsOpened, - download: this.downloadControlIsOpened, - filters: this.filterViewIsOpened, - copyright: this.copyrightViewIsOpened, - currentview: this.currentViewControlIsOpened, - userinfo: this.userInfoIsOpened, - } - const self = this this.userInfoIsOpened.addCallback((isOpen) => { if (!isOpen) { @@ -49,10 +41,16 @@ export class DefaultGuiState { } }) - sources[Hash.hash.data?.toLowerCase()]?.setData(true) + this.sources[Hash.hash.data?.toLowerCase()]?.setData(true) if (Hash.hash.data === "" || Hash.hash.data === undefined) { this.welcomeMessageIsOpened.setData(true) } } + + public closeAll() { + for (const sourceKey in this.sources) { + this.sources[sourceKey].setData(false) + } + } } diff --git a/UI/Image/DeleteImage.ts b/UI/Image/DeleteImage.ts index 636750607d..93f182e229 100644 --- a/UI/Image/DeleteImage.ts +++ b/UI/Image/DeleteImage.ts @@ -13,7 +13,7 @@ export default class DeleteImage extends Toggle { constructor( key: string, tags: Store, - state: { layoutToUse: LayoutConfig; changes?: Changes; osmConnection?: OsmConnection } + state: { layout: LayoutConfig; changes?: Changes; osmConnection?: OsmConnection } ) { const oldValue = tags.data[key] const isDeletedBadge = Translations.t.image.isDeleted @@ -24,7 +24,7 @@ export default class DeleteImage extends Toggle { await state?.changes?.applyAction( new ChangeTagAction(tags.data.id, new Tag(key, oldValue), tags.data, { changeType: "delete-image", - theme: state.layoutToUse.id, + theme: state.layout.id, }) ) }) @@ -39,7 +39,7 @@ export default class DeleteImage extends Toggle { await state?.changes?.applyAction( new ChangeTagAction(tags.data.id, new Tag(key, ""), tags.data, { changeType: "answer", - theme: state.layoutToUse.id, + theme: state.layout.id, }) ) }) diff --git a/UI/Image/ImageCarousel.ts b/UI/Image/ImageCarousel.ts index c9ca57f3c9..8818252d7e 100644 --- a/UI/Image/ImageCarousel.ts +++ b/UI/Image/ImageCarousel.ts @@ -14,7 +14,7 @@ export class ImageCarousel extends Toggle { constructor( images: Store<{ key: string; url: string; provider: ImageProvider }[]>, tags: Store, - state: { osmConnection?: OsmConnection; changes?: Changes; layoutToUse: LayoutConfig } + state: { osmConnection?: OsmConnection; changes?: Changes; layout: LayoutConfig } ) { const uiElements = images.map( (imageURLS: { key: string; url: string; provider: ImageProvider }[]) => { diff --git a/UI/Image/ImageUploadFlow.ts b/UI/Image/ImageUploadFlow.ts index 84011ce966..30842a14ad 100644 --- a/UI/Image/ImageUploadFlow.ts +++ b/UI/Image/ImageUploadFlow.ts @@ -11,26 +11,19 @@ import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { FixedUiElement } from "../Base/FixedUiElement" import { VariableUiElement } from "../Base/VariableUIElement" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import { OsmConnection } from "../../Logic/Osm/OsmConnection" -import { Changes } from "../../Logic/Osm/Changes" import Loading from "../Base/Loading" import { LoginToggle } from "../Popup/LoginButton" import Constants from "../../Models/Constants" import { DefaultGuiState } from "../DefaultGuiState" import ScrollableFullScreen from "../Base/ScrollableFullScreen" +import { SpecialVisualizationState } from "../SpecialVisualization" export class ImageUploadFlow extends Toggle { private static readonly uploadCountsPerId = new Map>() constructor( tagsSource: Store, - state: { - osmConnection: OsmConnection - layoutToUse: LayoutConfig - changes: Changes - featureSwitchUserbadge: Store - }, + state: SpecialVisualizationState, imagePrefix: string = "image", text: string = undefined ) { @@ -56,7 +49,7 @@ export class ImageUploadFlow extends Toggle { await state.changes.applyAction( new ChangeTagAction(tags.id, new Tag(key, url), tagsSource.data, { changeType: "add-image", - theme: state.layoutToUse.id, + theme: state.layout.id, }) ) console.log("Adding image:" + key, url) @@ -111,7 +104,7 @@ export class ImageUploadFlow extends Toggle { const tags = tagsSource.data - const layout = state?.layoutToUse + const layout = state?.layout let matchingLayer: LayerConfig = undefined for (const layer of layout?.layers ?? []) { if (layer.source.osmTags.matchesProperties(tags)) { diff --git a/UI/Input/LengthInput.ts b/UI/Input/LengthInput.ts index 21d813681d..1e538896e1 100644 --- a/UI/Input/LengthInput.ts +++ b/UI/Input/LengthInput.ts @@ -1,31 +1,30 @@ import { InputElement } from "./InputElement" -import { UIEventSource } from "../../Logic/UIEventSource" +import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" import Combine from "../Base/Combine" import Svg from "../../Svg" -import { Utils } from "../../Utils" import Loc from "../../Models/Loc" import { GeoOperations } from "../../Logic/GeoOperations" -import Minimap, { MinimapObj } from "../Base/Minimap" import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch" import BaseUIElement from "../BaseUIElement" +import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers" /** * Selects a length after clicking on the minimap, in meters */ export default class LengthInput extends InputElement { - private readonly _location: UIEventSource + private readonly _location: Store private readonly value: UIEventSource - private readonly background: UIEventSource + private readonly background: Store constructor( - mapBackground: UIEventSource, location: UIEventSource, + mapBackground?: UIEventSource, value?: UIEventSource ) { super() this._location = location this.value = value ?? new UIEventSource(undefined) - this.background = mapBackground + this.background = mapBackground ?? new ImmutableStore(AvailableRasterLayers.osmCarto) this.SetClass("block") } @@ -41,28 +40,26 @@ export default class LengthInput extends InputElement { protected InnerConstructElement(): HTMLElement { let map: BaseUIElement & MinimapObj = undefined let layerControl: BaseUIElement = undefined - if (!Utils.runningFromConsole) { - map = Minimap.createMiniMap({ - background: this.background, - allowMoving: false, - location: this._location, - attribution: true, - leafletOptions: { - tap: true, - }, - }) + map = Minimap.createMiniMap({ + background: this.background, + allowMoving: false, + location: this._location, + attribution: true, + leafletOptions: { + tap: true, + }, + }) - layerControl = new BackgroundMapSwitch( - { - locationControl: this._location, - backgroundLayer: this.background, - }, - this.background, - { - allowedCategories: ["map", "photo"], - } - ) - } + layerControl = new BackgroundMapSwitch( + { + locationControl: this._location, + backgroundLayer: this.background, + }, + this.background, + { + allowedCategories: ["map", "photo"], + } + ) const crosshair = new Combine([ Svg.length_crosshair_svg().SetStyle( `position: absolute;top: 0;left: 0;transform:rotate(${this.value.data ?? 0}deg);` diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index 45c57fb3b5..4cc8864f6e 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -3,7 +3,7 @@ import * as EmailValidator from "email-validator" import { parsePhoneNumberFromString } from "libphonenumber-js" import { InputElement } from "./InputElement" import { TextField } from "./TextField" -import { Store, UIEventSource } from "../../Logic/UIEventSource" +import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" import CombinedInputElement from "./CombinedInputElement" import SimpleDatePicker from "./SimpleDatePicker" import OpeningHoursInput from "../OpeningHours/OpeningHoursInput" @@ -25,6 +25,7 @@ import InputElementMap from "./InputElementMap" import Translations from "../i18n/Translations" import { Translation } from "../i18n/Translation" import Locale from "../i18n/Locale" +import { RasterLayerPolygon } from "../../Models/RasterLayers" export class TextFieldDef { public readonly name: string @@ -638,7 +639,7 @@ class LengthTextField extends TextFieldDef { location?: [number, number] args?: string[] feature?: any - mapBackgroundLayer?: Store + mapBackgroundLayer?: Store } ) => { options = options ?? {} @@ -674,14 +675,18 @@ class LengthTextField extends TextFieldDef { zoom: zoom, }) if (args[1]) { - // We have a prefered map! + // The arguments indicate the preferred background type options.mapBackgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo( location, - new UIEventSource(args[1].split(",")) + new ImmutableStore(args[1].split(",")) ) } const background = options?.mapBackgroundLayer - const li = new LengthInput(new UIEventSource(background.data), location, value) + const li = new LengthInput( + new UIEventSource(background.data), + location, + value + ) li.SetStyle("height: 20rem;") return li } diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index a4e6c15975..f3232168b1 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -21,12 +21,20 @@ export class MapLibreAdaptor implements MapProperties { "keyboard", "touchZoomRotate", ] + private static maplibre_zoom_handlers = [ + "scrollZoom", + "boxZoom", + "doubleClickZoom", + "touchZoomRotate", + ] readonly location: UIEventSource<{ lon: number; lat: number }> readonly zoom: UIEventSource - readonly bounds: Store + readonly bounds: UIEventSource readonly rasterLayer: UIEventSource readonly maxbounds: UIEventSource readonly allowMoving: UIEventSource + readonly allowZooming: UIEventSource + readonly lastClickLocation: Store private readonly _maplibreMap: Store private readonly _bounds: UIEventSource /** @@ -50,11 +58,14 @@ export class MapLibreAdaptor implements MapProperties { }) this.maxbounds = state?.maxbounds ?? new UIEventSource(undefined) this.allowMoving = state?.allowMoving ?? new UIEventSource(true) + this.allowZooming = state?.allowZooming ?? new UIEventSource(true) this._bounds = new UIEventSource(undefined) this.bounds = this._bounds this.rasterLayer = state?.rasterLayer ?? new UIEventSource(undefined) + const lastClickLocation = new UIEventSource<{ lon: number; lat: number }>(undefined) + this.lastClickLocation = lastClickLocation const self = this maplibreMap.addCallbackAndRunD((map) => { map.on("load", () => { @@ -63,11 +74,13 @@ export class MapLibreAdaptor implements MapProperties { self.SetZoom(self.zoom.data) self.setMaxBounds(self.maxbounds.data) self.setAllowMoving(self.allowMoving.data) + self.setAllowZooming(self.allowZooming.data) }) self.MoveMapToCurrentLoc(self.location.data) self.SetZoom(self.zoom.data) self.setMaxBounds(self.maxbounds.data) self.setAllowMoving(self.allowMoving.data) + self.setAllowZooming(self.allowZooming.data) map.on("moveend", () => { const dt = this.location.data dt.lon = map.getCenter().lng @@ -81,6 +94,11 @@ export class MapLibreAdaptor implements MapProperties { ]) self._bounds.setData(bbox) }) + map.on("click", (e) => { + const lon = e.lngLat.lng + const lat = e.lngLat.lat + lastClickLocation.setData({ lon, lat }) + }) }) this.rasterLayer.addCallback((_) => @@ -95,6 +113,8 @@ export class MapLibreAdaptor implements MapProperties { this.zoom.addCallbackAndRunD((z) => self.SetZoom(z)) this.maxbounds.addCallbackAndRun((bbox) => self.setMaxBounds(bbox)) this.allowMoving.addCallbackAndRun((allowMoving) => self.setAllowMoving(allowMoving)) + this.allowZooming.addCallbackAndRun((allowZooming) => self.setAllowZooming(allowZooming)) + this.bounds.addCallbackAndRunD((bounds) => self.setBounds(bounds)) } /** @@ -205,7 +225,7 @@ export class MapLibreAdaptor implements MapProperties { // already the correct background layer, nothing to do return } - if (background === undefined) { + if (!background?.url) { // no background to set this.removeCurrentLayer(map) this._currentRasterLayer = undefined @@ -266,4 +286,38 @@ export class MapLibreAdaptor implements MapProperties { } } } + + private setAllowZooming(allow: true | boolean | undefined) { + const map = this._maplibreMap.data + if (map === undefined) { + return + } + if (allow === false) { + for (const id of MapLibreAdaptor.maplibre_zoom_handlers) { + map[id].disable() + } + } else { + for (const id of MapLibreAdaptor.maplibre_zoom_handlers) { + map[id].enable() + } + } + } + + private setBounds(bounds: BBox) { + const map = this._maplibreMap.data + if (map === undefined) { + return + } + const oldBounds = map.getBounds() + const e = 0.0000001 + const hasDiff = + Math.abs(oldBounds.getWest() - bounds.getWest()) > e && + Math.abs(oldBounds.getEast() - bounds.getEast()) > e && + Math.abs(oldBounds.getNorth() - bounds.getNorth()) > e && + Math.abs(oldBounds.getSouth() - bounds.getSouth()) > e + if (!hasDiff) { + return + } + map.fitBounds(bounds.toLngLat()) + } } diff --git a/UI/Map/ShowDataLayer.ts b/UI/Map/ShowDataLayer.ts index 5694ae2d69..3032a4eff3 100644 --- a/UI/Map/ShowDataLayer.ts +++ b/UI/Map/ShowDataLayer.ts @@ -1,4 +1,4 @@ -import { ImmutableStore, Store } from "../../Logic/UIEventSource" +import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" import type { Map as MlMap } from "maplibre-gl" import { GeoJSONSource, Marker } from "maplibre-gl" import { ShowDataLayerOptions } from "./ShowDataLayerOptions" @@ -14,10 +14,13 @@ import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" import { Utils } from "../../Utils" import * as range_layer from "../../assets/layers/range/range.json" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" +import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter" +import FilteredLayer from "../../Models/FilteredLayer" +import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" class PointRenderingLayer { private readonly _config: PointRenderingConfig - private readonly _fetchStore?: (id: string) => Store + private readonly _fetchStore?: (id: string) => Store> private readonly _map: MlMap private readonly _onClick: (feature: Feature) => void private readonly _allMarkers: Map = new Map() @@ -27,7 +30,7 @@ class PointRenderingLayer { features: FeatureSource, config: PointRenderingConfig, visibility?: Store, - fetchStore?: (id: string) => Store, + fetchStore?: (id: string) => Store>, onClick?: (feature: Feature) => void ) { this._config = config @@ -96,7 +99,7 @@ class PointRenderingLayer { } private addPoint(feature: Feature, loc: [number, number]): Marker { - let store: Store + let store: Store> if (this._fetchStore) { store = this._fetchStore(feature.properties.id) } else { @@ -143,7 +146,7 @@ class LineRenderingLayer { private readonly _map: MlMap private readonly _config: LineRenderingConfig private readonly _visibility?: Store - private readonly _fetchStore?: (id: string) => Store + private readonly _fetchStore?: (id: string) => Store> private readonly _onClick?: (feature: Feature) => void private readonly _layername: string private readonly _listenerInstalledOn: Set = new Set() @@ -154,7 +157,7 @@ class LineRenderingLayer { layername: string, config: LineRenderingConfig, visibility?: Store, - fetchStore?: (id: string) => Store, + fetchStore?: (id: string) => Store>, onClick?: (feature: Feature) => void ) { this._layername = layername @@ -212,9 +215,10 @@ class LineRenderingLayer { promoteId: "id", }) // @ts-ignore + const linelayer = this._layername + "_line" map.addLayer({ source: this._layername, - id: this._layername + "_line", + id: linelayer, type: "line", paint: { "line-color": ["feature-state", "color"], @@ -227,9 +231,10 @@ class LineRenderingLayer { }, }) + const polylayer = this._layername + "_polygon" map.addLayer({ source: this._layername, - id: this._layername + "_polygon", + id: polylayer, type: "fill", filter: ["in", ["geometry-type"], ["literal", ["Polygon", "MultiPolygon"]]], layout: {}, @@ -238,6 +243,11 @@ class LineRenderingLayer { "fill-opacity": 0.1, }, }) + + this._visibility.addCallbackAndRunD((visible) => { + map.setLayoutProperty(linelayer, "visibility", visible ? "visible" : "none") + map.setLayoutProperty(polylayer, "visibility", visible ? "visible" : "none") + }) } else { src.setData({ type: "FeatureCollection", @@ -295,6 +305,24 @@ export default class ShowDataLayer { map.addCallbackAndRunD((map) => self.initDrawFeatures(map)) } + public static showMultipleLayers( + mlmap: UIEventSource, + features: FeatureSource, + layers: LayerConfig[], + options?: Partial + ) { + const perLayer = new PerLayerFeatureSourceSplitter( + layers.map((l) => new FilteredLayer(l)), + new StaticFeatureSource(features) + ) + perLayer.forEach((fs) => { + new ShowDataLayer(mlmap, { + layer: fs.layer.layerDef, + features: fs, + ...(options ?? {}), + }) + }) + } public static showRange( map: Store, features: FeatureSource, @@ -318,8 +346,11 @@ export default class ShowDataLayer { } private initDrawFeatures(map: MlMap) { - const { features, doShowLayer, fetchStore, selectedElement } = this._options - const onClick = (feature: Feature) => selectedElement?.setData(feature) + let { features, doShowLayer, fetchStore, selectedElement, selectedLayer } = this._options + const onClick = (feature: Feature) => { + selectedElement?.setData(feature) + selectedLayer?.setData(this._options.layer) + } for (let i = 0; i < this._options.layer.lineRendering.length; i++) { const lineRenderingConfig = this._options.layer.lineRendering[i] new LineRenderingLayer( diff --git a/UI/Map/ShowDataLayerOptions.ts b/UI/Map/ShowDataLayerOptions.ts index 524e9847c9..c20960a72e 100644 --- a/UI/Map/ShowDataLayerOptions.ts +++ b/UI/Map/ShowDataLayerOptions.ts @@ -1,6 +1,8 @@ import FeatureSource from "../../Logic/FeatureSource/FeatureSource" import { Store, UIEventSource } from "../../Logic/UIEventSource" import { OsmTags } from "../../Models/OsmFeature" +import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import { Feature } from "geojson" export interface ShowDataLayerOptions { /** @@ -11,7 +13,12 @@ export interface ShowDataLayerOptions { * Indication of the current selected element; overrides some filters. * When a feature is tapped, the feature will be put in there */ - selectedElement?: UIEventSource + selectedElement?: UIEventSource + + /** + * When a feature of this layer is tapped, the layer will be marked + */ + selectedLayer?: UIEventSource /** * If set, zoom to the features when initially loaded and when they are changed @@ -26,5 +33,5 @@ export interface ShowDataLayerOptions { * Function which fetches the relevant store. * If given, the map will update when a property is changed */ - fetchStore?: (id: string) => UIEventSource + fetchStore?: (id: string) => Store> } diff --git a/UI/Map/ShowDataMultiLayer.ts b/UI/Map/ShowDataMultiLayer.ts deleted file mode 100644 index 677e71d760..0000000000 --- a/UI/Map/ShowDataMultiLayer.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * SHows geojson on the given leaflet map, but attempts to figure out the correct layer first - */ -import { ImmutableStore, Store } from "../../Logic/UIEventSource" -import ShowDataLayer from "./ShowDataLayer" -import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter" -import FilteredLayer from "../../Models/FilteredLayer" -import { ShowDataLayerOptions } from "./ShowDataLayerOptions" -import { Map as MlMap } from "maplibre-gl" -import FilteringFeatureSource from "../../Logic/FeatureSource/Sources/FilteringFeatureSource" -import { GlobalFilter } from "../../Models/GlobalFilter" - -export default class ShowDataMultiLayer { - constructor( - map: Store, - options: ShowDataLayerOptions & { - layers: FilteredLayer[] - globalFilters?: Store - } - ) { - new PerLayerFeatureSourceSplitter( - new ImmutableStore(options.layers), - (features, layer) => { - const newOptions = { - ...options, - layer: layer.layerDef, - features: new FilteringFeatureSource( - layer, - features, - options.fetchStore, - options.globalFilters - ), - } - new ShowDataLayer(map, newOptions) - }, - options.features - ) - } -} diff --git a/UI/NewPoint/ConfirmLocationOfPoint.ts b/UI/NewPoint/ConfirmLocationOfPoint.ts index 47a203ecbc..dd2c6f4bba 100644 --- a/UI/NewPoint/ConfirmLocationOfPoint.ts +++ b/UI/NewPoint/ConfirmLocationOfPoint.ts @@ -1,6 +1,4 @@ import { UIEventSource } from "../../Logic/UIEventSource" -import { OsmConnection } from "../../Logic/Osm/OsmConnection" -import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" import BaseUIElement from "../BaseUIElement" import LocationInput from "../Input/LocationInput" import { BBox } from "../../Logic/BBox" @@ -18,18 +16,13 @@ import { Tag } from "../../Logic/Tags/Tag" import { WayId } from "../../Models/OsmFeature" import { Translation } from "../i18n/Translation" import { Feature } from "geojson" -import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers" -import { GlobalFilter } from "../../Logic/State/GlobalFilter" +import { AvailableRasterLayers } from "../../Models/RasterLayers" +import { SpecialVisualizationState } from "../SpecialVisualization" +import ClippedFeatureSource from "../../Logic/FeatureSource/Sources/ClippedFeatureSource" export default class ConfirmLocationOfPoint extends Combine { constructor( - state: { - globalFilters: UIEventSource - featureSwitchIsTesting: UIEventSource - osmConnection: OsmConnection - featurePipeline: FeaturePipeline - backgroundLayer?: UIEventSource - }, + state: SpecialVisualizationState, filterViewIsOpened: UIEventSource, preset: PresetInfo, confirmText: BaseUIElement, @@ -55,7 +48,7 @@ export default class ConfirmLocationOfPoint extends Combine { const locationSrc = new UIEventSource(zloc) let backgroundLayer = new UIEventSource( - state?.backgroundLayer?.data ?? AvailableRasterLayers.osmCarto + state?.mapProperties.rasterLayer?.data ?? AvailableRasterLayers.osmCarto ) if (preset.preciseInput.preferredBackground) { const defaultBackground = AvailableRasterLayers.SelectBestLayerAccordingTo( @@ -105,15 +98,13 @@ export default class ConfirmLocationOfPoint extends Combine { Math.max(preset.boundsFactor ?? 0.25, 2) ) loadedBbox = bbox - const allFeatures: Feature[] = [] - preset.preciseInput.snapToLayers.forEach((layerId) => { - console.log("Snapping to", layerId) - state.featurePipeline - .GetFeaturesWithin(layerId, bbox) - ?.forEach((feats) => allFeatures.push(...(feats))) - }) - console.log("Snapping to", allFeatures) - snapToFeatures.setData(allFeatures) + const sources = preset.preciseInput.snapToLayers.map( + (layerId) => + new ClippedFeatureSource( + state.perLayer.get(layerId), + bbox.asGeoJson({}) + ) + ) }) } } diff --git a/UI/OpeningHours/OpeningHours.ts b/UI/OpeningHours/OpeningHours.ts index 8587609b2d..a0ce4ec007 100644 --- a/UI/OpeningHours/OpeningHours.ts +++ b/UI/OpeningHours/OpeningHours.ts @@ -488,7 +488,7 @@ export class OH { } public static CreateOhObject( - tags: object & { _lat: number; _lon: number; _country?: string }, + tags: Record & { _lat: number; _lon: number; _country?: string }, textToParse: string ) { // noinspection JSPotentiallyInvalidConstructorUsage diff --git a/UI/OpeningHours/OpeningHoursVisualization.ts b/UI/OpeningHours/OpeningHoursVisualization.ts index 011d59bb22..be9acd840e 100644 --- a/UI/OpeningHours/OpeningHoursVisualization.ts +++ b/UI/OpeningHours/OpeningHoursVisualization.ts @@ -23,7 +23,7 @@ export default class OpeningHoursVisualization extends Toggle { ] constructor( - tags: UIEventSource, + tags: UIEventSource>, state: { osmConnection?: OsmConnection }, key: string, prefix = "", @@ -49,7 +49,7 @@ export default class OpeningHoursVisualization extends Toggle { } try { return OpeningHoursVisualization.CreateFullVisualisation( - OH.CreateOhObject(tags.data, ohtext) + OH.CreateOhObject(tags.data, ohtext) ) } catch (e) { console.warn(e, e.stack) diff --git a/UI/Popup/AddNoteCommentViz.ts b/UI/Popup/AddNoteCommentViz.ts index 8f0eb7ac97..0749d5cca9 100644 --- a/UI/Popup/AddNoteCommentViz.ts +++ b/UI/Popup/AddNoteCommentViz.ts @@ -8,7 +8,8 @@ import Toggle from "../Input/Toggle" import { LoginToggle } from "./LoginButton" import Combine from "../Base/Combine" import Title from "../Base/Title" -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import { UIEventSource } from "../../Logic/UIEventSource" export class AddNoteCommentViz implements SpecialVisualization { funcName = "add_note_comment" @@ -21,7 +22,11 @@ export class AddNoteCommentViz implements SpecialVisualization { }, ] - public constr(state, tags, args) { + public constr( + state: SpecialVisualizationState, + tags: UIEventSource>, + args: string[] + ) { const t = Translations.t.notes const textField = new TextField({ placeholder: t.addCommentPlaceholder, @@ -62,12 +67,11 @@ export class AddNoteCommentViz implements SpecialVisualization { return t.addCommentAndClose }) ) - ).onClick(() => { + ).onClick(async () => { const id = tags.data[args[1] ?? "id"] - state.osmConnection.closeNote(id, txt.data).then((_) => { - tags.data["closed_at"] = new Date().toISOString() - tags.ping() - }) + await state.osmConnection.closeNote(id, txt.data) + tags.data["closed_at"] = new Date().toISOString() + tags.ping() }) const reopen = new SubtleButton( @@ -80,12 +84,11 @@ export class AddNoteCommentViz implements SpecialVisualization { return t.reopenNoteAndComment }) ) - ).onClick(() => { + ).onClick(async () => { const id = tags.data[args[1] ?? "id"] - state.osmConnection.reopenNote(id, txt.data).then((_) => { - tags.data["closed_at"] = undefined - tags.ping() - }) + await state.osmConnection.reopenNote(id, txt.data) + tags.data["closed_at"] = undefined + tags.ping() }) const isClosed = tags.map((tags) => (tags["closed_at"] ?? "") !== "") diff --git a/UI/Popup/AutoApplyButton.ts b/UI/Popup/AutoApplyButton.ts index 51d004e516..47d1b633f1 100644 --- a/UI/Popup/AutoApplyButton.ts +++ b/UI/Popup/AutoApplyButton.ts @@ -1,7 +1,5 @@ -import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import BaseUIElement from "../BaseUIElement" import { Stores, UIEventSource } from "../../Logic/UIEventSource" -import { DefaultGuiState } from "../DefaultGuiState" import { SubtleButton } from "../Base/SubtleButton" import Img from "../Base/Img" import { FixedUiElement } from "../Base/FixedUiElement" @@ -9,8 +7,6 @@ import Combine from "../Base/Combine" import Link from "../Base/Link" import { SubstitutedTranslation } from "../SubstitutedTranslation" import { Utils } from "../../Utils" -import Minimap from "../Base/Minimap" -import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import { VariableUiElement } from "../Base/VariableUIElement" import Loading from "../Base/Loading" @@ -23,15 +19,21 @@ import FilteredLayer from "../../Models/FilteredLayer" import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" import Lazy from "../Base/Lazy" import List from "../Base/List" -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource" +import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" +import ShowDataLayer from "../Map/ShowDataLayer" +import SvelteUIElement from "../Base/SvelteUIElement" +import MaplibreMap from "../Map/MaplibreMap.svelte" export interface AutoAction extends SpecialVisualization { supportsAutoAction: boolean applyActionOn( state: { - layoutToUse: LayoutConfig + layout: LayoutConfig changes: Changes + indexedFeatures: IndexedFeatureSource }, tagSource: UIEventSource, argument: string[] @@ -43,7 +45,7 @@ class ApplyButton extends UIElement { private readonly text: string private readonly targetTagRendering: string private readonly target_layer_id: string - private readonly state: FeaturePipelineState + private readonly state: SpecialVisualizationState private readonly target_feature_ids: string[] private readonly buttonState = new UIEventSource< "idle" | "running" | "done" | { error: string } @@ -52,7 +54,7 @@ class ApplyButton extends UIElement { private readonly tagRenderingConfig: TagRenderingConfig constructor( - state: FeaturePipelineState, + state: SpecialVisualizationState, target_feature_ids: string[], options: { target_layer_id: string @@ -68,9 +70,7 @@ class ApplyButton extends UIElement { this.targetTagRendering = options.targetTagRendering this.text = options.text this.icon = options.icon - this.layer = this.state.filteredLayers.data.find( - (l) => l.layerDef.id === this.target_layer_id - ) + this.layer = this.state.layerState.filteredLayers.get(this.target_layer_id) this.tagRenderingConfig = this.layer.layerDef.tagRenderings.find( (tr) => tr.id === this.targetTagRendering ) @@ -101,22 +101,23 @@ class ApplyButton extends UIElement { ), ]).SetClass("subtle") - const previewMap = Minimap.createMiniMap({ - allowMoving: false, - background: this.state.backgroundLayer, - addLayerControl: true, - }).SetClass("h-48") + const mlmap = new UIEventSource(undefined) + const mla = new MapLibreAdaptor(mlmap, { + rasterLayer: this.state.mapProperties.rasterLayer, + }) + mla.allowZooming.setData(false) + mla.allowMoving.setData(false) + + const previewMap = new SvelteUIElement(MaplibreMap, { map: mlmap }).SetClass("h-48") const features = this.target_feature_ids.map((id) => - this.state.allElements.ContainingFeatures.get(id) + this.state.indexedFeatures.featuresById.data.get(id) ) - new ShowDataLayer({ - leafletMap: previewMap.leafletMap, - zoomToFeatures: true, + new ShowDataLayer(mlmap, { features: StaticFeatureSource.fromGeojson(features), - state: this.state, - layerToShow: this.layer.layerDef, + zoomToFeatures: true, + layer: this.layer.layerDef, }) return new VariableUiElement( @@ -144,7 +145,7 @@ class ApplyButton extends UIElement { console.log("Applying auto-action on " + this.target_feature_ids.length + " features") for (const targetFeatureId of this.target_feature_ids) { - const featureTags = this.state.allElements.getEventSourceById(targetFeatureId) + const featureTags = this.state.featureProperties.getStore(targetFeatureId) const rendering = this.tagRenderingConfig.GetRenderValue(featureTags.data).txt const specialRenderings = Utils.NoNull( SubstitutedTranslation.ExtractSpecialComponents(rendering).map((x) => x.special) @@ -153,8 +154,8 @@ class ApplyButton extends UIElement { if (specialRenderings.length == 0) { console.warn( "AutoApply: feature " + - targetFeatureId + - " got a rendering without supported auto actions:", + targetFeatureId + + " got a rendering without supported auto actions:", rendering ) } @@ -224,7 +225,7 @@ export default class AutoApplyButton implements SpecialVisualization { "To effectively use this button, you'll need some ingredients:", new List([ "A target layer with features for which an action is defined in a tag rendering. The following special visualisations support an autoAction: " + - supportedActions.join(", "), + supportedActions.join(", "), "A host feature to place the auto-action on. This can be a big outline (such as a city). Another good option for this is the layer ", new Link("current_view", "./BuiltinLayers.md#current_view"), "Then, use a calculated tag on the host feature to determine the overlapping object ids", @@ -234,18 +235,17 @@ export default class AutoApplyButton implements SpecialVisualization { } constr( - state: FeaturePipelineState, - tagSource: UIEventSource, - argument: string[], - guistate: DefaultGuiState + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[] ): BaseUIElement { try { if ( - !state.layoutToUse.official && + !state.layout.official && !( state.featureSwitchIsTesting.data || state.osmConnection._oauth_config.url === - OsmConnection.oauth_configs["osm-test"].url + OsmConnection.oauth_configs["osm-test"].url ) ) { const t = Translations.t.general.add.import diff --git a/UI/Popup/CloseNoteButton.ts b/UI/Popup/CloseNoteButton.ts index e5556c8310..eb83d4c173 100644 --- a/UI/Popup/CloseNoteButton.ts +++ b/UI/Popup/CloseNoteButton.ts @@ -1,4 +1,3 @@ -import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import BaseUIElement from "../BaseUIElement" import Translations from "../i18n/Translations" import { Utils } from "../../Utils" @@ -7,7 +6,8 @@ import Img from "../Base/Img" import { SubtleButton } from "../Base/SubtleButton" import Toggle from "../Input/Toggle" import { LoginToggle } from "./LoginButton" -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import { UIEventSource } from "../../Logic/UIEventSource" export class CloseNoteButton implements SpecialVisualization { public readonly funcName = "close_note" @@ -43,7 +43,11 @@ export class CloseNoteButton implements SpecialVisualization { }, ] - public constr(state: FeaturePipelineState, tags, args): BaseUIElement { + public constr( + state: SpecialVisualizationState, + tags: UIEventSource>, + args: string[] + ): BaseUIElement { const t = Translations.t.notes const params: { @@ -78,7 +82,7 @@ export class CloseNoteButton implements SpecialVisualization { closeButton = new Toggle( closeButton, params.zoomButton ?? "", - state.locationControl.map((l) => l.zoom >= Number(params.minZoom)) + state.mapProperties.zoom.map((zoom) => zoom >= Number(params.minZoom)) ) } diff --git a/UI/Popup/ExportAsGpxViz.ts b/UI/Popup/ExportAsGpxViz.ts index e8340824cf..eb5241c510 100644 --- a/UI/Popup/ExportAsGpxViz.ts +++ b/UI/Popup/ExportAsGpxViz.ts @@ -4,14 +4,15 @@ import Svg from "../../Svg" import Combine from "../Base/Combine" import { GeoOperations } from "../../Logic/GeoOperations" import { Utils } from "../../Utils" -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import { UIEventSource } from "../../Logic/UIEventSource" export class ExportAsGpxViz implements SpecialVisualization { funcName = "export_as_gpx" docs = "Exports the selected feature as GPX-file" args = [] - constr(state, tagSource) { + constr(state: SpecialVisualizationState, tagSource: UIEventSource>) { const t = Translations.t.general.download return new SubtleButton( @@ -23,10 +24,10 @@ export class ExportAsGpxViz implements SpecialVisualization { ).onClick(() => { console.log("Exporting as GPX!") const tags = tagSource.data - const feature = state.allElements.ContainingFeatures.get(tags.id) - const matchingLayer = state?.layoutToUse?.getMatchingLayer(tags) - const gpx = GeoOperations.AsGpx(feature, matchingLayer) - const title = matchingLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track" + const feature = state.indexedFeatures.featuresById.data.get(tags.id) + const layer = state?.layout?.getMatchingLayer(tags) + const gpx = GeoOperations.AsGpx(feature, { layer }) + const title = layer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track" Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", { mimetype: "{gpx=application/gpx+xml}", }) diff --git a/UI/Popup/HistogramViz.ts b/UI/Popup/HistogramViz.ts index 27602743f8..d83d1dcf69 100644 --- a/UI/Popup/HistogramViz.ts +++ b/UI/Popup/HistogramViz.ts @@ -1,9 +1,8 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" -import { FixedUiElement } from "../Base/FixedUiElement" -// import Histogram from "../BigComponents/Histogram"; -// import {SpecialVisualization} from "../SpecialVisualization"; +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import Histogram from "../BigComponents/Histogram" -export class HistogramViz { +export class HistogramViz implements SpecialVisualization { funcName = "histogram" docs = "Create a histogram for a list of given values, read from the properties." example = @@ -30,7 +29,11 @@ export class HistogramViz { }, ] - constr(state, tagSource: UIEventSource, args: string[]) { + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + args: string[] + ) { let assignColors = undefined if (args.length >= 3) { const colors = [...args] @@ -63,10 +66,8 @@ export class HistogramViz { return undefined } }) - return new FixedUiElement("HISTORGRAM") - /* return new Histogram(listSource, args[1], args[2], { assignColor: assignColors, - })*/ + }) } } diff --git a/UI/Popup/ImportButton.ts b/UI/Popup/ImportButton.ts index 31c463f0d4..b2ff72073b 100644 --- a/UI/Popup/ImportButton.ts +++ b/UI/Popup/ImportButton.ts @@ -1,51 +1,47 @@ -import BaseUIElement from "../BaseUIElement" -import { SubtleButton } from "../Base/SubtleButton" -import { UIEventSource } from "../../Logic/UIEventSource" -import Combine from "../Base/Combine" -import { VariableUiElement } from "../Base/VariableUIElement" -import Translations from "../i18n/Translations" -import Toggle from "../Input/Toggle" -import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction" -import Loading from "../Base/Loading" -import { OsmConnection } from "../../Logic/Osm/OsmConnection" -import Lazy from "../Base/Lazy" -import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint" -import Img from "../Base/Img" -import FilteredLayer from "../../Models/FilteredLayer" -import { FixedUiElement } from "../Base/FixedUiElement" -import Svg from "../../Svg" -import { Utils } from "../../Utils" -import Minimap from "../Base/Minimap" -import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" -import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" -import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer" -import CreateWayWithPointReuseAction, { - MergePointConfig, -} from "../../Logic/Osm/Actions/CreateWayWithPointReuseAction" -import OsmChangeAction from "../../Logic/Osm/Actions/OsmChangeAction" -import FeatureSource from "../../Logic/FeatureSource/FeatureSource" -import { OsmObject, OsmWay } from "../../Logic/Osm/OsmObject" -import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" -import { DefaultGuiState } from "../DefaultGuiState" -import { PresetInfo } from "../BigComponents/SimpleAddUI" -import { TagUtils } from "../../Logic/Tags/TagUtils" -import { And } from "../../Logic/Tags/And" -import ReplaceGeometryAction from "../../Logic/Osm/Actions/ReplaceGeometryAction" -import CreateMultiPolygonWithPointReuseAction from "../../Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction" -import { Tag } from "../../Logic/Tags/Tag" -import TagApplyButton from "./TagApplyButton" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import conflation_json from "../../assets/layers/conflation/conflation.json" -import { GeoOperations } from "../../Logic/GeoOperations" -import { LoginToggle } from "./LoginButton" -import { AutoAction } from "./AutoApplyButton" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import { Changes } from "../../Logic/Osm/Changes" -import { ElementStorage } from "../../Logic/ElementStorage" -import Hash from "../../Logic/Web/Hash" -import { PreciseInput } from "../../Models/ThemeConfig/PresetConfig" -import { SpecialVisualization } from "../SpecialVisualization" +import BaseUIElement from "../BaseUIElement"; +import { SubtleButton } from "../Base/SubtleButton"; +import { UIEventSource } from "../../Logic/UIEventSource"; +import Combine from "../Base/Combine"; +import { VariableUiElement } from "../Base/VariableUIElement"; +import Translations from "../i18n/Translations"; +import Toggle from "../Input/Toggle"; +import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction"; +import Loading from "../Base/Loading"; +import { OsmConnection } from "../../Logic/Osm/OsmConnection"; +import Lazy from "../Base/Lazy"; +import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint"; +import Img from "../Base/Img"; +import FilteredLayer from "../../Models/FilteredLayer"; +import { FixedUiElement } from "../Base/FixedUiElement"; +import Svg from "../../Svg"; +import { Utils } from "../../Utils"; +import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; +import CreateWayWithPointReuseAction, { MergePointConfig } from "../../Logic/Osm/Actions/CreateWayWithPointReuseAction"; +import OsmChangeAction, { OsmCreateAction } from "../../Logic/Osm/Actions/OsmChangeAction"; +import FeatureSource from "../../Logic/FeatureSource/FeatureSource"; +import { OsmObject, OsmWay } from "../../Logic/Osm/OsmObject"; +import { PresetInfo } from "../BigComponents/SimpleAddUI"; +import { TagUtils } from "../../Logic/Tags/TagUtils"; +import { And } from "../../Logic/Tags/And"; +import ReplaceGeometryAction from "../../Logic/Osm/Actions/ReplaceGeometryAction"; +import CreateMultiPolygonWithPointReuseAction from "../../Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction"; +import { Tag } from "../../Logic/Tags/Tag"; +import TagApplyButton from "./TagApplyButton"; +import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; +import conflation_json from "../../assets/layers/conflation/conflation.json"; +import { GeoOperations } from "../../Logic/GeoOperations"; +import { LoginToggle } from "./LoginButton"; +import { AutoAction } from "./AutoApplyButton"; +import Hash from "../../Logic/Web/Hash"; +import { PreciseInput } from "../../Models/ThemeConfig/PresetConfig"; +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"; import Maproulette from "../../Logic/Maproulette"; +import { Feature, Point } from "geojson"; +import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"; +import ShowDataLayer from "../Map/ShowDataLayer"; +import { MapLibreAdaptor } from "../Map/MapLibreAdaptor"; +import SvelteUIElement from "../Base/SvelteUIElement"; +import MaplibreMap from "../Map/MaplibreMap.svelte"; /** * A helper class for the various import-flows. @@ -106,7 +102,7 @@ ${Utils.special_visualizations_importRequirementDocs} } abstract constructElement( - state: FeaturePipelineState, + state: SpecialVisualizationState, args: { max_snap_distance: string snap_onto_layers: string @@ -116,13 +112,16 @@ ${Utils.special_visualizations_importRequirementDocs} newTags: UIEventSource targetLayer: string }, - tagSource: UIEventSource, - guiState: DefaultGuiState, - feature: any, + tagSource: UIEventSource>, + feature: Feature, onCancelClicked: () => void ): BaseUIElement - constr(state, tagSource: UIEventSource, argsRaw, guiState) { + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argsRaw: string[] + ) { /** * Some generic import button pre-validation is implemented here: * - Are we logged in? @@ -139,7 +138,7 @@ ${Utils.special_visualizations_importRequirementDocs} { // Some initial validation if ( - !state.layoutToUse.official && + !state.layout.official && !( state.featureSwitchIsTesting.data || state.osmConnection._oauth_config.url === @@ -148,11 +147,9 @@ ${Utils.special_visualizations_importRequirementDocs} ) { return new Combine([t.officialThemesOnly.SetClass("alert"), t.howToTest]) } - const targetLayer: FilteredLayer = state.filteredLayers.data.filter( - (fl) => fl.layerDef.id === args.targetLayer - )[0] + const targetLayer: FilteredLayer = state.layerState.filteredLayers.get(args.targetLayer) if (targetLayer === undefined) { - const e = `Target layer not defined: error in import button for theme: ${state.layoutToUse.id}: layer ${args.targetLayer} not found` + const e = `Target layer not defined: error in import button for theme: ${state.layout.id}: layer ${args.targetLayer} not found` console.error(e) return new FixedUiElement(e).SetClass("alert") } @@ -167,7 +164,7 @@ ${Utils.special_visualizations_importRequirementDocs} const inviteToImportButton = new SubtleButton(img, args.text) const id = tagSource.data.id - const feature = state.allElements.ContainingFeatures.get(id) + const feature = state.indexedFeatures.featuresById.data.get(id) // Explanation of the tags that will be applied onto the imported/conflated object @@ -205,22 +202,13 @@ ${Utils.special_visualizations_importRequirementDocs} return tags._imported === "yes" }) - /**** THe actual panel showing the import guiding map ****/ - const importGuidingPanel = this.constructElement( - state, - args, - tagSource, - guiState, - feature, - () => importClicked.setData(false) + /**** The actual panel showing the import guiding map ****/ + const importGuidingPanel = this.constructElement(state, args, tagSource, feature, () => + importClicked.setData(false) ) const importFlow = new Toggle( - new Toggle( - new Loading(t0.stillLoading), - importGuidingPanel, - state.featurePipeline.runningQuery - ), + new Toggle(new Loading(t0.stillLoading), importGuidingPanel, state.dataIsLoading), inviteToImportButton, importClicked ) @@ -230,7 +218,7 @@ ${Utils.special_visualizations_importRequirementDocs} new Toggle( new Toggle(t.hasBeenImported, importFlow, isImported), t.zoomInMore.SetClass("alert block"), - state.locationControl.map((l) => l.zoom >= 18) + state.mapProperties.zoom.map((zoom) => zoom >= 18) ), pleaseLoginButton, state @@ -258,8 +246,13 @@ ${Utils.special_visualizations_importRequirementDocs} protected abstract canBeImported(feature: any) + private static readonly conflationLayer = new LayerConfig( + conflation_json, + "all_known_layers", + true + ) protected createConfirmPanelForWay( - state: FeaturePipelineState, + state: SpecialVisualizationState, args: { max_snap_distance: string snap_onto_layers: string @@ -270,32 +263,32 @@ ${Utils.special_visualizations_importRequirementDocs} }, feature: any, originalFeatureTags: UIEventSource, - action: OsmChangeAction & { getPreview(): Promise; newElementId?: string }, + action: OsmChangeAction & { getPreview?(): Promise; newElementId?: string }, onCancel: () => void ): BaseUIElement { const self = this - const confirmationMap = Minimap.createMiniMap({ - allowMoving: state.featureSwitchIsDebugging.data ?? false, - background: state.backgroundLayer, + const map = new UIEventSource(undefined) + new MapLibreAdaptor(map, { + allowMoving: UIEventSource.feedFrom(state.featureSwitchIsTesting), + allowZooming: UIEventSource.feedFrom(state.featureSwitchIsTesting), + rasterLayer: state.mapProperties.rasterLayer, }) + const confirmationMap = new SvelteUIElement(MaplibreMap, { map }) confirmationMap.SetStyle("height: 20rem; overflow: hidden").SetClass("rounded-xl") - // SHow all relevant data - including (eventually) the way of which the geometry will be replaced - new ShowDataMultiLayer({ - leafletMap: confirmationMap.leafletMap, - zoomToFeatures: true, - features: StaticFeatureSource.fromGeojson([feature]), - state: state, - layers: state.filteredLayers, - }) + ShowDataLayer.showMultipleLayers( + map, + new StaticFeatureSource([feature]), + state.layout.layers, + { zoomToFeatures: true } + ) + // Show all relevant data - including (eventually) the way of which the geometry will be replaced action.getPreview().then((changePreview) => { - new ShowDataLayer({ - leafletMap: confirmationMap.leafletMap, + new ShowDataLayer(map, { zoomToFeatures: false, features: changePreview, - state, - layerToShow: new LayerConfig(conflation_json, "all_known_layers", true), + layer: AbstractImportButton.conflationLayer, }) }) @@ -317,9 +310,9 @@ ${Utils.special_visualizations_importRequirementDocs} { originalFeatureTags.data["_imported"] = "yes" originalFeatureTags.ping() // will set isImported as per its definition - state.changes.applyAction(action) + await state.changes.applyAction(action) const newId = action.newElementId ?? action.mainObjectId - state.selectedElement.setData(state.allElements.ContainingFeatures.get(newId)) + state.selectedElement.setData(state.indexedFeatures.featuresById.data.get(newId)) } }) @@ -392,7 +385,7 @@ export class ConflateButton extends AbstractImportButton { } constructElement( - state: FeaturePipelineState, + state: SpecialVisualizationState, args: { max_snap_distance: string snap_onto_layers: string @@ -403,8 +396,7 @@ export class ConflateButton extends AbstractImportButton { targetLayer: string }, tagSource: UIEventSource, - guiState: DefaultGuiState, - feature: any, + feature: Feature, onCancelClicked: () => void ): BaseUIElement { const nodesMustMatch = args.snap_onto_layers @@ -424,10 +416,15 @@ export class ConflateButton extends AbstractImportButton { const key = args["way_to_conflate"] const wayToConflate = tagSource.data[key] feature = GeoOperations.removeOvernoding(feature) - const action = new ReplaceGeometryAction(state, feature, wayToConflate, { - theme: state.layoutToUse.id, - newTags: args.newTags.data, - }) + const action: OsmChangeAction & { getPreview(): Promise } = new ReplaceGeometryAction( + state, + feature, + wayToConflate, + { + theme: state.layout.id, + newTags: args.newTags.data, + } + ) return this.createConfirmPanelForWay( state, @@ -498,9 +495,9 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction newTags: UIEventSource targetLayer: string }, - state: FeaturePipelineState, + state: SpecialVisualizationState, mergeConfigs: any[] - ) { + ): OsmCreateAction & { getPreview(): Promise; newElementId?: string } { const coors = feature.geometry.coordinates if (feature.geometry.type === "Polygon" && coors.length > 1) { const outer = coors[0] @@ -525,8 +522,8 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction } async applyActionOn( - state: { layoutToUse: LayoutConfig; changes: Changes; allElements: ElementStorage }, - originalFeatureTags: UIEventSource, + state: SpecialVisualizationState, + originalFeatureTags: UIEventSource>, argument: string[] ): Promise { const id = originalFeatureTags.data.id @@ -535,14 +532,9 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction } AbstractImportButton.importedIds.add(originalFeatureTags.data.id) const args = this.parseArgs(argument, originalFeatureTags) - const feature = state.allElements.ContainingFeatures.get(id) + const feature = state.indexedFeatures.featuresById.data.get(id) const mergeConfigs = this.GetMergeConfig(args) - const action = ImportWayButton.CreateAction( - feature, - args, - state, - mergeConfigs - ) + const action = ImportWayButton.CreateAction(feature, args, state, mergeConfigs) await state.changes.applyAction(action) } @@ -557,7 +549,13 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction return deps } - constructElement(state, args, originalFeatureTags, guiState, feature, onCancel): BaseUIElement { + constructElement( + state: SpecialVisualizationState, + args, + originalFeatureTags: UIEventSource>, + feature, + onCancel + ): BaseUIElement { const geometry = feature.geometry if (!(geometry.type == "LineString" || geometry.type === "Polygon")) { @@ -567,7 +565,12 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction // Upload the way to OSM const mergeConfigs = this.GetMergeConfig(args) - let action = ImportWayButton.CreateAction(feature, args, state, mergeConfigs) + let action: OsmCreateAction & {getPreview?: any} = ImportWayButton.CreateAction( + feature, + args, + state, + mergeConfigs + ) return this.createConfirmPanelForWay( state, args, @@ -663,10 +666,9 @@ export class ImportPointButton extends AbstractImportButton { note_id: string maproulette_id: string }, - state: FeaturePipelineState, - guiState: DefaultGuiState, + state: SpecialVisualizationState, originalFeatureTags: UIEventSource, - feature: any, + feature: Feature, onCancel: () => void, close: () => void ): BaseUIElement { @@ -690,7 +692,7 @@ export class ImportPointButton extends AbstractImportButton { } const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, { - theme: state.layoutToUse.id, + theme: state.layout.id, changeType: "import", snapOnto: snapOnto, specialMotivation: specialMotivation, @@ -698,7 +700,7 @@ export class ImportPointButton extends AbstractImportButton { await state.changes.applyAction(newElementAction) state.selectedElement.setData( - state.allElements.ContainingFeatures.get(newElementAction.newElementId) + state.indexedFeatures.featuresById.data.get(newElementAction.newElementId) ) Hash.hash.setData(newElementAction.newElementId) @@ -742,19 +744,17 @@ export class ImportPointButton extends AbstractImportButton { const presetInfo = { tags: args.newTags.data, icon: () => new Img(args.icon), - layerToAddTo: state.filteredLayers.data.filter( - (l) => l.layerDef.id === args.targetLayer - )[0], + layerToAddTo: state.layerState.filteredLayers.get(args.targetLayer), name: args.text, title: Translations.T(args.text), preciseInput: preciseInputSpec, // must be explicitely assigned, if 'undefined' won't work otherwise boundsFactor: 3, } - const [lon, lat] = feature.geometry.coordinates + const [lon, lat] = <[number,number]> feature.geometry.coordinates return new ConfirmLocationOfPoint( state, - guiState.filterViewIsOpened, + state.guistate.filterViewIsOpened, presetInfo, Translations.W(args.text), { @@ -783,10 +783,9 @@ export class ImportPointButton extends AbstractImportButton { } constructElement( - state, + state: SpecialVisualizationState, args, originalFeatureTags, - guiState, feature, onCancel: () => void ): BaseUIElement { @@ -797,7 +796,6 @@ export class ImportPointButton extends AbstractImportButton { ImportPointButton.createConfirmPanelForPoint( args, state, - guiState, originalFeatureTags, feature, onCancel, diff --git a/UI/Popup/LanguageElement.ts b/UI/Popup/LanguageElement.ts index 12bd75bd62..b07e90232f 100644 --- a/UI/Popup/LanguageElement.ts +++ b/UI/Popup/LanguageElement.ts @@ -1,9 +1,7 @@ -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import BaseUIElement from "../BaseUIElement" import { UIEventSource } from "../../Logic/UIEventSource" -import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import { VariableUiElement } from "../Base/VariableUIElement" -import { OsmTags } from "../../Models/OsmFeature" import all_languages from "../../assets/language_translations.json" import { Translation } from "../i18n/Translation" import Combine from "../Base/Combine" @@ -16,10 +14,9 @@ import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import { And } from "../../Logic/Tags/And" import { Tag } from "../../Logic/Tags/Tag" import { EditButton, SaveButton } from "./SaveButton" -import { FixedUiElement } from "../Base/FixedUiElement" import Translations from "../i18n/Translations" import Toggle from "../Input/Toggle" -import { On } from "../../Models/ThemeConfig/Conversion/Conversion" +import { Feature } from "geojson" export class LanguageElement implements SpecialVisualization { funcName: string = "language_chooser" @@ -79,9 +76,10 @@ export class LanguageElement implements SpecialVisualization { ` constr( - state: FeaturePipelineState, - tagSource: UIEventSource, - argument: string[] + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature ): BaseUIElement { let [key, question, item_render, single_render, all_render, on_no_known_languages, mode] = argument @@ -172,7 +170,7 @@ export class LanguageElement implements SpecialVisualization { new And(selection), tagSource.data, { - theme: state?.layoutToUse?.id ?? "unkown", + theme: state?.layout?.id ?? "unkown", changeType: "answer", } ) diff --git a/UI/Popup/MapillaryLinkVis.ts b/UI/Popup/MapillaryLinkVis.ts index e3c401a523..721e6be73c 100644 --- a/UI/Popup/MapillaryLinkVis.ts +++ b/UI/Popup/MapillaryLinkVis.ts @@ -2,7 +2,9 @@ import { GeoOperations } from "../../Logic/GeoOperations" import { MapillaryLink } from "../BigComponents/MapillaryLink" import { UIEventSource } from "../../Logic/UIEventSource" import Loc from "../../Models/Loc" -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import { Feature } from "geojson" +import BaseUIElement from "../BaseUIElement" export class MapillaryLinkVis implements SpecialVisualization { funcName = "mapillary_link" @@ -15,9 +17,13 @@ export class MapillaryLinkVis implements SpecialVisualization { }, ] - public constr(state, tagsSource, args) { - const feat = state.allElements.ContainingFeatures.get(tagsSource.data.id) - const [lon, lat] = GeoOperations.centerpointCoordinates(feat) + public constr( + state: SpecialVisualizationState, + tagsSource: UIEventSource>, + args: string[], + feature: Feature + ): BaseUIElement { + const [lon, lat] = GeoOperations.centerpointCoordinates(feature) let zoom = Number(args[0]) if (isNaN(zoom)) { zoom = 18 diff --git a/UI/Popup/MinimapViz.ts b/UI/Popup/MinimapViz.ts index 1a53373a6a..ccf061f68f 100644 --- a/UI/Popup/MinimapViz.ts +++ b/UI/Popup/MinimapViz.ts @@ -1,9 +1,14 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" -import Loc from "../../Models/Loc" -import Minimap from "../Base/Minimap" -import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import { Feature } from "geojson" +import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" +import SvelteUIElement from "../Base/SvelteUIElement" +import MaplibreMap from "../Map/MaplibreMap.svelte" +import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter" +import FilteredLayer from "../../Models/FilteredLayer" +import ShowDataLayer from "../Map/ShowDataLayer" +import { stat } from "fs" export class MinimapViz implements SpecialVisualization { funcName = "minimap" @@ -22,16 +27,20 @@ export class MinimapViz implements SpecialVisualization { ] example: "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`" - constr(state, tagSource, args, _) { + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + args: string[] + ) { if (state === undefined) { return undefined } const keys = [...args] keys.splice(0, 1) - const featureStore = state.allElements.ContainingFeatures - const featuresToShow: Store<{ freshness: Date; feature: any }[]> = tagSource.map( - (properties) => { - const features: { freshness: Date; feature: any }[] = [] + const featuresToShow: Store = state.indexedFeatures.featuresById.map( + (featuresById) => { + const properties = tagSource.data + const features: Feature[] = [] for (const key of keys) { const value = properties[key] if (value === undefined || value === null) { @@ -45,21 +54,22 @@ export class MinimapViz implements SpecialVisualization { } for (const id of idList) { - const feature = featureStore.get(id) + const feature = featuresById.get(id) if (feature === undefined) { console.warn("No feature found for id ", id) continue } - features.push({ - freshness: new Date(), - feature, - }) + features.push(feature) } } return features - } + }, + [tagSource] ) - const properties = tagSource.data + + const mlmap = new UIEventSource(undefined) + const mla = new MapLibreAdaptor(mlmap) + let zoom = 18 if (args[0]) { const parsed = Number(args[0]) @@ -67,33 +77,18 @@ export class MinimapViz implements SpecialVisualization { zoom = parsed } } - const locationSource = new UIEventSource({ - lat: Number(properties._lat), - lon: Number(properties._lon), - zoom: zoom, - }) - const minimap = Minimap.createMiniMap({ - background: state.backgroundLayer, - location: locationSource, - allowMoving: false, - }) + mla.zoom.setData(zoom) + mla.allowMoving.setData(false) + mla.allowZooming.setData(false) - locationSource.addCallback((loc) => { - if (loc.zoom > zoom) { - // We zoom back - locationSource.data.zoom = zoom - locationSource.ping() - } - }) + ShowDataLayer.showMultipleLayers( + mlmap, + new StaticFeatureSource(featuresToShow), + state.layout.layers + ) - new ShowDataMultiLayer({ - leafletMap: minimap["leafletMap"], - zoomToFeatures: true, - layers: state.filteredLayers, - features: new StaticFeatureSource(featuresToShow), - }) - - minimap.SetStyle("overflow: hidden; pointer-events: none;") - return minimap + return new SvelteUIElement(MaplibreMap, { map: mlmap }).SetStyle( + "overflow: hidden; pointer-events: none;" + ) } } diff --git a/UI/Popup/MultiApply.ts b/UI/Popup/MultiApply.ts index df6829d8c2..ca0a5382b6 100644 --- a/UI/Popup/MultiApply.ts +++ b/UI/Popup/MultiApply.ts @@ -2,17 +2,14 @@ import { Store } from "../../Logic/UIEventSource" import BaseUIElement from "../BaseUIElement" import Combine from "../Base/Combine" import { SubtleButton } from "../Base/SubtleButton" -import { Changes } from "../../Logic/Osm/Changes" import { FixedUiElement } from "../Base/FixedUiElement" import Translations from "../i18n/Translations" import { VariableUiElement } from "../Base/VariableUIElement" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import { Tag } from "../../Logic/Tags/Tag" -import { ElementStorage } from "../../Logic/ElementStorage" import { And } from "../../Logic/Tags/And" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import Toggle from "../Input/Toggle" -import { OsmConnection } from "../../Logic/Osm/OsmConnection" +import { SpecialVisualizationState } from "../SpecialVisualization" export interface MultiApplyParams { featureIds: Store @@ -21,12 +18,7 @@ export interface MultiApplyParams { autoapply: boolean overwrite: boolean tagsSource: Store - state: { - changes: Changes - allElements: ElementStorage - layoutToUse: LayoutConfig - osmConnection: OsmConnection - } + state: SpecialVisualizationState } class MultiApplyExecutor { @@ -68,14 +60,14 @@ class MultiApplyExecutor { console.log("Multi-applying changes...") const featuresToChange = this.params.featureIds.data const changes = this.params.state.changes - const allElements = this.params.state.allElements + const allElements = this.params.state.featureProperties const keysToChange = this.params.keysToApply const overwrite = this.params.overwrite const selfTags = this.params.tagsSource.data - const theme = this.params.state.layoutToUse.id + const theme = this.params.state.layout.id for (const id of featuresToChange) { const tagsToApply: Tag[] = [] - const otherFeatureTags = allElements.getEventSourceById(id).data + const otherFeatureTags = allElements.getStore(id).data for (const key of keysToChange) { const newValue = selfTags[key] if (newValue === undefined) { diff --git a/UI/Popup/MultiApplyViz.ts b/UI/Popup/MultiApplyViz.ts index c481afdcf9..ad9357f22d 100644 --- a/UI/Popup/MultiApplyViz.ts +++ b/UI/Popup/MultiApplyViz.ts @@ -1,6 +1,6 @@ -import { Store } from "../../Logic/UIEventSource" +import { Store, UIEventSource } from "../../Logic/UIEventSource" import MultiApply from "./MultiApply" -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" export class MultiApplyViz implements SpecialVisualization { funcName = "multi_apply" @@ -31,7 +31,11 @@ export class MultiApplyViz implements SpecialVisualization { example = "{multi_apply(_features_with_the_same_name_within_100m, name:etymology:wikidata;name:etymology, Apply etymology information on all nearby objects with the same name)}" - constr(state, tagsSource, args) { + constr( + state: SpecialVisualizationState, + tagsSource: UIEventSource>, + args: string[] + ) { const featureIdsKey = args[0] const keysToApply = args[1].split(";") const text = args[2] diff --git a/UI/Popup/NearbyImageVis.ts b/UI/Popup/NearbyImageVis.ts index 7a66ecde39..52e359c3b5 100644 --- a/UI/Popup/NearbyImageVis.ts +++ b/UI/Popup/NearbyImageVis.ts @@ -1,6 +1,4 @@ -import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import { UIEventSource } from "../../Logic/UIEventSource" -import { DefaultGuiState } from "../DefaultGuiState" import BaseUIElement from "../BaseUIElement" import Translations from "../i18n/Translations" import { GeoOperations } from "../../Logic/GeoOperations" @@ -19,7 +17,7 @@ import { VariableUiElement } from "../Base/VariableUIElement" import Toggle from "../Input/Toggle" import Title from "../Base/Title" import { MapillaryLinkVis } from "./MapillaryLinkVis" -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" export class NearbyImageVis implements SpecialVisualization { args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [ @@ -39,14 +37,13 @@ export class NearbyImageVis implements SpecialVisualization { funcName = "nearby_images" constr( - state: FeaturePipelineState, - tagSource: UIEventSource, - args: string[], - guistate: DefaultGuiState + state: SpecialVisualizationState, + tagSource: UIEventSource>, + args: string[] ): BaseUIElement { const t = Translations.t.image.nearbyPictures const mode: "open" | "expandable" | "collapsable" = args[0] - const feature = state.allElements.ContainingFeatures.get(tagSource.data.id) + const feature = state.indexedFeatures.featuresById.data.get(tagSource.data.id) const [lon, lat] = GeoOperations.centerpointCoordinates(feature) const id: string = tagSource.data["id"] const canBeEdited: boolean = !!id?.match("(node|way|relation)/-?[0-9]+") @@ -69,7 +66,7 @@ export class NearbyImageVis implements SpecialVisualization { } await state?.changes?.applyAction( new ChangeTagAction(id, new And(tags), tagSource.data, { - theme: state?.layoutToUse.id, + theme: state?.layout.id, changeType: "link-image", }) ) @@ -116,8 +113,8 @@ export class NearbyImageVis implements SpecialVisualization { maxDaysOld: 365 * 3, } const slideshow = canBeEdited - ? new SelectOneNearbyImage(options, state) - : new NearbyImages(options, state) + ? new SelectOneNearbyImage(options, state.indexedFeatures) + : new NearbyImages(options, state.indexedFeatures) const controls = new Combine([ towardsCenter, new Combine([ diff --git a/UI/Popup/NearbyImages.ts b/UI/Popup/NearbyImages.ts index e216574837..6ddc1ab6f5 100644 --- a/UI/Popup/NearbyImages.ts +++ b/UI/Popup/NearbyImages.ts @@ -13,9 +13,10 @@ import Translations from "../i18n/Translations" import { Mapillary } from "../../Logic/ImageProviders/Mapillary" import { SubtleButton } from "../Base/SubtleButton" import { GeoOperations } from "../../Logic/GeoOperations" -import { ElementStorage } from "../../Logic/ElementStorage" import Lazy from "../Base/Lazy" import P4C from "pic4carto" +import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource" + export interface P4CPicture { pictureUrl: string date?: number @@ -47,15 +48,15 @@ export interface NearbyImageOptions { } class ImagesInLoadedDataFetcher { - private allElements: ElementStorage + private indexedFeatures: IndexedFeatureSource - constructor(state: { allElements: ElementStorage }) { - this.allElements = state.allElements + constructor(indexedFeatures: IndexedFeatureSource) { + this.indexedFeatures = indexedFeatures } public fetchAround(loc: { lon: number; lat: number; searchRadius?: number }): P4CPicture[] { const foundImages: P4CPicture[] = [] - this.allElements.ContainingFeatures.forEach((feature) => { + this.indexedFeatures.features.data.forEach((feature) => { const props = feature.properties const images = [] if (props.image) { @@ -100,7 +101,7 @@ class ImagesInLoadedDataFetcher { } export default class NearbyImages extends Lazy { - constructor(options: NearbyImageOptions, state?: { allElements: ElementStorage }) { + constructor(options: NearbyImageOptions, state?: IndexedFeatureSource) { super(() => { const t = Translations.t.image.nearbyPictures const shownImages = options.shownImagesCount ?? new UIEventSource(25) @@ -171,10 +172,7 @@ export default class NearbyImages extends Lazy { ) } - private static buildPictureFetcher( - options: NearbyImageOptions, - state?: { allElements: ElementStorage } - ) { + private static buildPictureFetcher(options: NearbyImageOptions, state?: IndexedFeatureSource) { const picManager = new P4C.PicturesManager({}) const searchRadius = options.searchRadius ?? 500 @@ -283,7 +281,7 @@ export class SelectOneNearbyImage extends NearbyImages implements InputElement

}, - state?: { allElements: ElementStorage } + state?: IndexedFeatureSource ) { super(options, state) this.value = options.value ?? new UIEventSource(undefined) diff --git a/UI/Popup/PlantNetDetectionViz.ts b/UI/Popup/PlantNetDetectionViz.ts index fcbd521094..206131673e 100644 --- a/UI/Popup/PlantNetDetectionViz.ts +++ b/UI/Popup/PlantNetDetectionViz.ts @@ -12,7 +12,7 @@ import Combine from "../Base/Combine" import Svg from "../../Svg" import Translations from "../i18n/Translations" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"; export class PlantNetDetectionViz implements SpecialVisualization { funcName = "plantnet_detection" @@ -27,7 +27,7 @@ export class PlantNetDetectionViz implements SpecialVisualization { }, ] - public constr(state, tags, args) { + public constr(state: SpecialVisualizationState, tags: UIEventSource>, args: string[]) { let imagePrefixes: string[] = undefined if (args.length > 0) { imagePrefixes = [].concat(...args.map((a) => a.split(","))) @@ -53,7 +53,7 @@ export class PlantNetDetectionViz implements SpecialVisualization { ]), tags.data, { - theme: state.layoutToUse.id, + theme: state.layout.id, changeType: "plantnet-ai-detection", } ) diff --git a/UI/Popup/ShareLinkViz.ts b/UI/Popup/ShareLinkViz.ts index 438aa2e283..c63cffcf16 100644 --- a/UI/Popup/ShareLinkViz.ts +++ b/UI/Popup/ShareLinkViz.ts @@ -3,7 +3,7 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import ShareButton from "../BigComponents/ShareButton" import Svg from "../../Svg" import { FixedUiElement } from "../Base/FixedUiElement" -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"; export class ShareLinkViz implements SpecialVisualization { funcName = "share_link" @@ -17,12 +17,12 @@ export class ShareLinkViz implements SpecialVisualization { }, ] - public constr(state, tagSource: UIEventSource, args) { + public constr(state: SpecialVisualizationState, tagSource: UIEventSource>, args: string[]) { if (window.navigator.share) { const generateShareData = () => { - const title = state?.layoutToUse?.title?.txt ?? "MapComplete" + const title = state?.layout?.title?.txt ?? "MapComplete" - let matchingLayer: LayerConfig = state?.layoutToUse?.getMatchingLayer( + let matchingLayer: LayerConfig = state?.layout?.getMatchingLayer( tagSource?.data ) let name = @@ -41,7 +41,7 @@ export class ShareLinkViz implements SpecialVisualization { return { title: name, url: url, - text: state?.layoutToUse?.shortDescription?.txt ?? "MapComplete", + text: state?.layout?.shortDescription?.txt ?? "MapComplete", } } diff --git a/UI/Popup/StealViz.ts b/UI/Popup/StealViz.ts index 28bdd3ed85..20e74ebca6 100644 --- a/UI/Popup/StealViz.ts +++ b/UI/Popup/StealViz.ts @@ -4,7 +4,7 @@ import { VariableUiElement } from "../Base/VariableUIElement" import BaseUIElement from "../BaseUIElement" import EditableTagRendering from "./EditableTagRendering" import Combine from "../Base/Combine" -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" export class StealViz implements SpecialVisualization { funcName = "steal" @@ -21,12 +21,12 @@ export class StealViz implements SpecialVisualization { required: true, }, ] - constr(state, featureTags, args) { + constr(state: SpecialVisualizationState, featureTags, args) { const [featureIdKey, layerAndtagRenderingIds] = args const tagRenderings: [LayerConfig, TagRenderingConfig][] = [] for (const layerAndTagRenderingId of layerAndtagRenderingIds.split(";")) { const [layerId, tagRenderingId] = layerAndTagRenderingId.trim().split(".") - const layer = state.layoutToUse.layers.find((l) => l.id === layerId) + const layer = state.layout.layers.find((l) => l.id === layerId) const tagRendering = layer.tagRenderings.find((tr) => tr.id === tagRenderingId) tagRenderings.push([layer, tagRendering]) } @@ -39,7 +39,7 @@ export class StealViz implements SpecialVisualization { if (featureId === undefined) { return undefined } - const otherTags = state.allElements.getEventSourceById(featureId) + const otherTags = state.featureProperties.getStore(featureId) const elements: BaseUIElement[] = [] for (const [layer, tagRendering] of tagRenderings) { const el = new EditableTagRendering( diff --git a/UI/Popup/TagApplyButton.ts b/UI/Popup/TagApplyButton.ts index 12fe157e3c..1027b224d1 100644 --- a/UI/Popup/TagApplyButton.ts +++ b/UI/Popup/TagApplyButton.ts @@ -11,10 +11,9 @@ import { And } from "../../Logic/Tags/And" import Toggle from "../Input/Toggle" import { Utils } from "../../Utils" import { Tag } from "../../Logic/Tags/Tag" -import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { Changes } from "../../Logic/Osm/Changes" -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" export default class TagApplyButton implements AutoAction, SpecialVisualization { public readonly funcName = "tag_apply" @@ -76,7 +75,10 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization return tgsSpec } - public static generateTagsToApply(spec: string, tagSource: Store): Store { + public static generateTagsToApply( + spec: string, + tagSource: Store> + ): Store { // Check whether we need to look up a single value if (!spec.includes(";") && !spec.includes("=") && spec.includes("$")) { @@ -110,7 +112,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization async applyActionOn( state: { - layoutToUse: LayoutConfig + layout: LayoutConfig changes: Changes }, tags: UIEventSource, @@ -125,7 +127,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization new And(tagsToApply.data), tags.data, // We pass in the tags of the selected element, not the tags of the target element! { - theme: state.layoutToUse.id, + theme: state.layout.id, changeType: "answer", } ) @@ -133,8 +135,8 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization } public constr( - state: FeaturePipelineState, - tags: UIEventSource, + state: SpecialVisualizationState, + tags: UIEventSource>, args: string[] ): BaseUIElement { const tagsToApply = TagApplyButton.generateTagsToApply(args[0], tags) @@ -162,9 +164,9 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization const applyButton = new SubtleButton( image, new Combine([msg, tagsExplanation]).SetClass("flex flex-col") - ).onClick(() => { - self.applyActionOn(state, tags, args) + ).onClick(async () => { applied.setData(true) + await self.applyActionOn(state, tags, args) }) return new Toggle( diff --git a/UI/Popup/TagRenderingAnswer.svelte b/UI/Popup/TagRenderingAnswer.svelte new file mode 100644 index 0000000000..e69de29bb2 diff --git a/UI/Popup/TagRenderingAnswer.ts b/UI/Popup/TagRenderingAnswer.ts index 3263c8e566..338be0c07c 100644 --- a/UI/Popup/TagRenderingAnswer.ts +++ b/UI/Popup/TagRenderingAnswer.ts @@ -6,6 +6,7 @@ import { SubstitutedTranslation } from "../SubstitutedTranslation" import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" import Combine from "../Base/Combine" import Img from "../Base/Img" +import { SpecialVisualisationState } from "../SpecialVisualization" /*** * Displays the correct value for a known tagrendering @@ -14,7 +15,7 @@ export default class TagRenderingAnswer extends VariableUiElement { constructor( tagsSource: UIEventSource, configuration: TagRenderingConfig, - state: any, + state: SpecialVisualisationState, contentClasses: string = "", contentStyle: string = "", options?: { @@ -24,6 +25,7 @@ export default class TagRenderingAnswer extends VariableUiElement { if (configuration === undefined) { throw "Trying to generate a tagRenderingAnswer without configuration..." } + UIEventSource if (tagsSource === undefined) { throw "Trying to generate a tagRenderingAnswer without tagSource..." } diff --git a/UI/Popup/UploadToOsmViz.ts b/UI/Popup/UploadToOsmViz.ts index 9fbd54760a..f25f723867 100644 --- a/UI/Popup/UploadToOsmViz.ts +++ b/UI/Popup/UploadToOsmViz.ts @@ -3,7 +3,8 @@ import { Feature } from "geojson" import { Point } from "@turf/turf" import { GeoLocationPointProperties } from "../../Logic/State/GeoLocationState" import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI" -import { SpecialVisualization } from "../SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import { UIEventSource } from "../../Logic/UIEventSource" /** * Wrapper around 'UploadTraceToOsmUI' @@ -14,15 +15,20 @@ export class UploadToOsmViz implements SpecialVisualization { "Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored." args = [] - constr(state, featureTags, args) { + constr( + state: SpecialVisualizationState, + featureTags: UIEventSource>, + args: string[] + ) { function getTrace(title: string) { title = title?.trim() if (title === undefined || title === "") { title = "Uploaded with MapComplete" } title = Utils.EncodeXmlValue(title) - const userLocations: Feature[] = - state.historicalUserLocations.features.data.map((f) => f.feature) + const userLocations = []>( + state.historicalUserLocations.features.data + ) const trackPoints: string[] = [] for (const l of userLocations) { let trkpt = ` ` diff --git a/UI/SpecialVisualization.ts b/UI/SpecialVisualization.ts index f41284b9bc..4e46c00080 100644 --- a/UI/SpecialVisualization.ts +++ b/UI/SpecialVisualization.ts @@ -1,18 +1,70 @@ -import { UIEventSource } from "../Logic/UIEventSource" +import { Store, UIEventSource } from "../Logic/UIEventSource" import BaseUIElement from "./BaseUIElement" -import FeaturePipelineState from "../Logic/State/FeaturePipelineState" import { DefaultGuiState } from "./DefaultGuiState" +import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" +import FeatureSource, { + IndexedFeatureSource, + WritableFeatureSource, +} from "../Logic/FeatureSource/FeatureSource" +import { OsmConnection } from "../Logic/Osm/OsmConnection" +import { Changes } from "../Logic/Osm/Changes" +import { MapProperties } from "../Models/MapProperties" +import LayerState from "../Logic/State/LayerState" +import { Feature } from "geojson" +import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" +import UserRelatedState from "../Logic/State/UserRelatedState" +import { MangroveIdentity } from "../Logic/Web/MangroveReviews" +import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" + +/** + * The state needed to render a special Visualisation. + */ +export interface SpecialVisualizationState { + readonly guistate: DefaultGuiState + readonly layout: LayoutConfig + + readonly layerState: LayerState + readonly featureProperties: { getStore(id: string): UIEventSource> } + + readonly indexedFeatures: IndexedFeatureSource + + readonly historicalUserLocations: WritableFeatureSource + + readonly osmConnection: OsmConnection + readonly featureSwitchUserbadge: Store + readonly featureSwitchIsTesting: Store + readonly changes: Changes + /** + * State of the main map + */ + readonly mapProperties: MapProperties + + readonly selectedElement: UIEventSource + + /** + * If data is currently being fetched from external sources + */ + readonly dataIsLoading: Store + /** + * Only needed for 'ReplaceGeometryAction' + */ + readonly fullNodeDatabase?: FullNodeDatabaseSource + + readonly perLayer: ReadonlyMap + readonly userRelatedState: { readonly mangroveIdentity: MangroveIdentity } +} export interface SpecialVisualization { funcName: string - constr: ( - state: FeaturePipelineState, - tagSource: UIEventSource, - argument: string[], - guistate: DefaultGuiState - ) => BaseUIElement docs: string | BaseUIElement example?: string args: { name: string; defaultValue?: string; doc: string; required?: false | boolean }[] getLayerDependencies?: (argument: string[]) => string[] + + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature + ): BaseUIElement } diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 18842bda1b..db4d1bd35c 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -44,15 +44,13 @@ import { LoginToggle } from "./Popup/LoginButton" import Toggle from "./Input/Toggle" import { SubstitutedTranslation } from "./SubstitutedTranslation" import List from "./Base/List" -import { OsmFeature } from "../Models/OsmFeature" -import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import { GeoOperations } from "../Logic/GeoOperations" import StatisticsPanel from "./BigComponents/StatisticsPanel" import AutoApplyButton from "./Popup/AutoApplyButton" import { LanguageElement } from "./Popup/LanguageElement" import FeatureReviews from "../Logic/Web/MangroveReviews" import Maproulette from "../Logic/Maproulette" import SvelteUIElement from "./Base/SvelteUIElement" +import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" export default class SpecialVisualizations { public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList() @@ -146,63 +144,17 @@ export default class SpecialVisualizations { new MultiApplyViz(), new ExportAsGpxViz(), new AddNoteCommentViz(), + new CloseNoteButton(), new PlantNetDetectionViz(), + + new TagApplyButton(), + new ImportPointButton(), new ImportWayButton(), new ConflateButton(), - new TagApplyButton(), - new CloseNoteButton(), + new NearbyImageVis(), - new MapillaryLinkVis(), - new LanguageElement(), - { - funcName: "all_tags", - docs: "Prints all key-value pairs of the object - used for debugging", - args: [], - constr: (state, tags: UIEventSource) => - new SvelteUIElement(AllTagsPanel, { tags, state }), - }, - { - funcName: "image_carousel", - docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)", - args: [ - { - name: "image_key", - defaultValue: AllImageProviders.defaultKeys.join(","), - doc: "The keys given to the images, e.g. if image is given, the first picture URL will be added as image, the second as image:0, the third as image:1, etc... Multiple values are allowed if ';'-separated ", - }, - ], - constr: (state, tags, args) => { - let imagePrefixes: string[] = undefined - if (args.length > 0) { - imagePrefixes = [].concat(...args.map((a) => a.split(","))) - } - return new ImageCarousel( - AllImageProviders.LoadImagesFor(tags, imagePrefixes), - tags, - state - ) - }, - }, - { - funcName: "image_upload", - docs: "Creates a button where a user can upload an image to IMGUR", - args: [ - { - name: "image-key", - doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)", - defaultValue: "image", - }, - { - name: "label", - doc: "The text to show on the button", - defaultValue: "Add image", - }, - ], - constr: (state, tags, args) => { - return new ImageUploadFlow(tags, state, args[0], args[1]) - }, - }, + { funcName: "wikipedia", docs: "A box showing the corresponding wikipedia article - based on the wikidata tag", @@ -267,6 +219,56 @@ export default class SpecialVisualizations { }) ), }, + new MapillaryLinkVis(), + new LanguageElement(), + { + funcName: "all_tags", + docs: "Prints all key-value pairs of the object - used for debugging", + args: [], + constr: (state, tags: UIEventSource) => + new SvelteUIElement(AllTagsPanel, { tags, state }), + }, + { + funcName: "image_carousel", + docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)", + args: [ + { + name: "image_key", + defaultValue: AllImageProviders.defaultKeys.join(","), + doc: "The keys given to the images, e.g. if image is given, the first picture URL will be added as image, the second as image:0, the third as image:1, etc... Multiple values are allowed if ';'-separated ", + }, + ], + constr: (state, tags, args) => { + let imagePrefixes: string[] = undefined + if (args.length > 0) { + imagePrefixes = [].concat(...args.map((a) => a.split(","))) + } + return new ImageCarousel( + AllImageProviders.LoadImagesFor(tags, imagePrefixes), + tags, + state + ) + }, + }, + { + funcName: "image_upload", + docs: "Creates a button where a user can upload an image to IMGUR", + args: [ + { + name: "image-key", + doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)", + defaultValue: "image", + }, + { + name: "label", + doc: "The text to show on the button", + defaultValue: "Add image", + }, + ], + constr: (state, tags, args) => { + return new ImageUploadFlow(tags, state, args[0], args[1]) + }, + }, { funcName: "reviews", docs: "Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten", @@ -283,14 +285,18 @@ export default class SpecialVisualizations { doc: "The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value", }, ], - constr: (state, tags, args) => { + constr: (state, tags, args, feature) => { const nameKey = args[0] ?? "name" let fallbackName = args[1] - const feature = state.allElements.ContainingFeatures.get(tags.data.id) - const mangrove = FeatureReviews.construct(feature, state, { - nameKey: nameKey, - fallbackName, - }) + const mangrove = FeatureReviews.construct( + feature, + tags, + state.userRelatedState.mangroveIdentity, + { + nameKey: nameKey, + fallbackName, + } + ) const form = new ReviewForm((r) => mangrove.createReview(r), state) return new ReviewElement(mangrove, form) @@ -348,7 +354,7 @@ export default class SpecialVisualizations { doc: "The path (or shorthand) that should be returned", }, ], - constr: (state, tagSource: UIEventSource, args) => { + constr: (_, tagSource: UIEventSource, args) => { const url = args[0] const shorthands = args[1] const neededValue = args[2] @@ -380,7 +386,7 @@ export default class SpecialVisualizations { return undefined } const allUnits = [].concat( - ...(state?.layoutToUse?.layers?.map((lyr) => lyr.units) ?? []) + ...(state?.layout?.layers?.map((lyr) => lyr.units) ?? []) ) const unit = allUnits.filter((unit) => unit.isApplicableToKey(key) @@ -409,8 +415,8 @@ export default class SpecialVisualizations { ).onClick(() => { console.log("Exporting as Geojson") const tags = tagSource.data - const feature = state.allElements.ContainingFeatures.get(tags.id) - const matchingLayer = state?.layoutToUse?.getMatchingLayer(tags) + const feature = state.indexedFeatures.featuresById.data.get(tags.id) + const matchingLayer = state?.layout?.getMatchingLayer(tags) const title = matchingLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson" const data = JSON.stringify(feature, null, " ") @@ -429,15 +435,15 @@ export default class SpecialVisualizations { docs: "Opens the current view in the iD-editor", args: [], constr: (state, feature) => { - return new OpenIdEditor(state, undefined, feature.data.id) + return new OpenIdEditor(state.mapProperties, undefined, feature.data.id) }, }, { funcName: "open_in_josm", docs: "Opens the current view in the JOSM-editor", args: [], - constr: (state, feature) => { - return new OpenJosm(state) + constr: (state) => { + return new OpenJosm(state.osmConnection, state.mapProperties.bounds) }, }, { @@ -560,10 +566,10 @@ export default class SpecialVisualizations { docs: "Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'", example: "`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`.", - constr: (state, tagsSource) => + constr: (state, tagsSource, args, feature) => new VariableUiElement( tagsSource.map((tags) => { - const layer = state.layoutToUse.getMatchingLayer(tags) + const layer = state.layout.getMatchingLayer(tags) const title = layer?.title?.GetRenderValue(tags) if (title === undefined) { return undefined @@ -575,7 +581,7 @@ export default class SpecialVisualizations { { funcName: "maproulette_task", args: [], - constr(state, tagSource, argument, guistate) { + constr(state, tagSource) { let parentId = tagSource.data.mr_challengeId if (parentId === undefined) { console.warn("Element ", tagSource.data.id, " has no mr_challengeId") @@ -666,7 +672,7 @@ export default class SpecialVisualizations { Number(maproulette_id), Number(status), { - tags: `MapComplete MapComplete:${state.layoutToUse.id}`, + tags: `MapComplete MapComplete:${state.layout.id}`, } ) tagsSource.data["mr_taskStatus"] = @@ -715,40 +721,19 @@ export default class SpecialVisualizations { docs: "Show general statistics about the elements currently in view. Intended to use on the `current_view`-layer", args: [], constr: (state, tagsSource, args, guiState) => { - const elementsInview = new UIEventSource< - { - distance: number - center: [number, number] - element: OsmFeature - layer: LayerConfig - }[] - >([]) - - function update() { - const mapCenter = <[number, number]>[ - state.locationControl.data.lon, - state.locationControl.data.lon, - ] - const bbox = state.currentBounds.data - const elements = state.featurePipeline - .getAllVisibleElementsWithmeta(bbox) - .map((el) => { - const distance = GeoOperations.distanceBetween(el.center, mapCenter) - return { ...el, distance } - }) - elements.sort((e0, e1) => e0.distance - e1.distance) - elementsInview.setData(elements) - } - - state.currentBounds.addCallbackAndRun(update) - state.featurePipeline.newDataLoadedSignal.addCallback(update) - state.filteredLayers.addCallbackAndRun((fls) => { - for (const fl of fls) { - fl.isDisplayed.addCallback(update) - fl.appliedFilters.addCallback(update) - } - }) - return new StatisticsPanel(elementsInview, state) + return new Combine( + state.layout.layers + .filter((l) => l.name !== null) + .map( + (l) => { + const fs = state.perLayer.get(l.id) + const bbox = state.mapProperties.bounds.data + const fsBboxed = new BBoxFeatureSourceForLayer(fs, bbox) + return new StatisticsPanel(fsBboxed) + }, + [state.mapProperties.bounds] + ) + ) }, }, { @@ -777,7 +762,7 @@ export default class SpecialVisualizations { required: true, }, ], - constr(state, tags, args) { + constr(__, tags, args) { return new VariableUiElement( tags.map((tags) => { const [to, subject, body, button_text] = args.map((str) => diff --git a/UI/SubstitutedTranslation.ts b/UI/SubstitutedTranslation.ts index d8480d3816..65e4cd0775 100644 --- a/UI/SubstitutedTranslation.ts +++ b/UI/SubstitutedTranslation.ts @@ -2,7 +2,7 @@ import { UIEventSource } from "../Logic/UIEventSource" import { Translation } from "./i18n/Translation" import Locale from "./i18n/Locale" import { FixedUiElement } from "./Base/FixedUiElement" -import SpecialVisualizations from "./SpecialVisualizations" +// import SpecialVisualizations from "./SpecialVisualizations" import { Utils } from "../Utils" import { VariableUiElement } from "./Base/VariableUIElement" import Combine from "./Base/Combine" @@ -10,13 +10,13 @@ import BaseUIElement from "./BaseUIElement" import { DefaultGuiState } from "./DefaultGuiState" import FeaturePipelineState from "../Logic/State/FeaturePipelineState" import LinkToWeblate from "./Base/LinkToWeblate" -import { SpecialVisualization } from "./SpecialVisualization" +import { SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization" export class SubstitutedTranslation extends VariableUiElement { public constructor( translation: Translation, tagsSource: UIEventSource>, - state: FeaturePipelineState, + state: SpecialVisualizationState, mapping: Map< string, | BaseUIElement @@ -78,7 +78,7 @@ export class SubstitutedTranslation extends VariableUiElement { } try { return viz.func - .constr(state, tagsSource, proto.special.args, DefaultGuiState.state) + .constr(state, tagsSource, proto.special.args) ?.SetStyle(proto.special.style) } catch (e) { console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e) @@ -125,7 +125,7 @@ export class SubstitutedTranslation extends VariableUiElement { } for (const knownSpecial of extraMappings.concat( - SpecialVisualizations.specialVisualizations + [] // TODO enable SpecialVisualizations.specialVisualizations )) { // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way' const matched = template.match( @@ -181,10 +181,10 @@ export class SubstitutedTranslation extends VariableUiElement { console.warn( "Found a suspicious special rendering value in: ", template, - " did you mean one of: ", - SpecialVisualizations.specialVisualizations + " did you mean one of: " + /*SpecialVisualizations.specialVisualizations .map((sp) => sp.funcName + "()") - .join(", ") + .join(", ")*/ ) } diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index a68e718b97..90daaae917 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -1,154 +1,66 @@

-
Hello world
-
- +
+ state.guistate.welcomeMessageIsOpened.setData(true)}> +
+ + + {layout.title} + +
+
+ state.guistate.menuIsOpened.setData(true)}> + +
-
+
+ state.guistate.filterViewIsOpened.setData(true)}> + +
@@ -160,11 +72,116 @@ - new GeolocationControl(geolocation, mapproperties).SetClass("block w-8 h-8")}> +
-
+
+ + +
+ +
+
state.guistate.filterViewIsOpened.setData(false)}>Close
+ + {#each layout.layers as layer} + + {/each} + + +
+
+ + + +
+
+
state.guistate.welcomeMessageIsOpened.setData(false)}>Close
+ + + selected ? "tab-selected" : "tab-unselected"}>About + selected ? "tab-selected" : "tab-unselected"}>Tab 2 + selected ? "tab-selected" : "tab-unselected"}>Tab 3 + + + + layout.description}> + {Translations.t.general.welcomeExplanation.general} + {#if layout.layers.some((l) => l.presets?.length > 0)} + + {Translations.t.general.welcomeExplanation.addNew} + + {/if} + + + layout.descriptionTail}> +
+ +
+ + +
+ Content 2 + Content 3 +
+
+
+
+
+ + + + +
+
+
state.guistate.menuIsOpened.setData(false)}>Close
+ + + selected ? "tab-selected" : "tab-unselected"}>About MapComplete + selected ? "tab-selected" : "tab-unselected"}>Settings + selected ? "tab-selected" : "tab-unselected"}>Privacy + + + + About MC + + + + User settings + Privacy + + +
+
+
+ + +
+ + + +
+
+ + diff --git a/Utils.ts b/Utils.ts index 8bc0e011cf..531516bc76 100644 --- a/Utils.ts +++ b/Utils.ts @@ -1317,4 +1317,34 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be // If the element has a parent, repeat the process for the parent element return Utils.findParentWithScrolling(element.parentElement) } + + /** + * Returns true if the contents of `a` are the same (and in the same order) as `b`. + * Might have false negatives in some cases + * @param a + * @param b + */ + public static sameList(a: ReadonlyArray, b: ReadonlyArray) { + if (a == b) { + return true + } + if (a === undefined || a === null || b === undefined || b === null) { + return false + } + if (a.length !== b.length) { + return false + } + for (let i = 0; i < a.length; i++) { + let ai = a[i] + let bi = b[i] + if (ai == bi) { + continue + } + if (ai === bi) { + continue + } + return false + } + return true + } } diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index d563745b40..2229edffe8 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -572,34 +572,58 @@ video { width: 100%; } +.\!container { + width: 100% !important; +} + @media (min-width: 640px) { .container { max-width: 640px; } + + .\!container { + max-width: 640px !important; + } } @media (min-width: 768px) { .container { max-width: 768px; } + + .\!container { + max-width: 768px !important; + } } @media (min-width: 1024px) { .container { max-width: 1024px; } + + .\!container { + max-width: 1024px !important; + } } @media (min-width: 1280px) { .container { max-width: 1280px; } + + .\!container { + max-width: 1280px !important; + } } @media (min-width: 1536px) { .container { max-width: 1536px; } + + .\!container { + max-width: 1536px !important; + } } .sr-only { @@ -626,6 +650,10 @@ video { visibility: visible; } +.\!visible { + visibility: visible !important; +} + .invisible { visibility: hidden; } @@ -823,10 +851,6 @@ video { margin-bottom: 0.75rem; } -.mb-4 { - margin-bottom: 1rem; -} - .mt-1 { margin-top: 0.25rem; } @@ -847,10 +871,26 @@ video { margin-top: 1rem; } +.mt-2 { + margin-top: 0.5rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + .ml-4 { margin-left: 1rem; } +.mt-6 { + margin-top: 1.5rem; +} + .mb-24 { margin-bottom: 6rem; } @@ -859,10 +899,6 @@ video { margin-left: 0.25rem; } -.mt-2 { - margin-top: 0.5rem; -} - .mb-2 { margin-bottom: 0.5rem; } @@ -871,10 +907,6 @@ video { margin-top: 3rem; } -.ml-2 { - margin-left: 0.5rem; -} - .ml-3 { margin-left: 0.75rem; } @@ -903,10 +935,6 @@ video { margin-top: 2rem; } -.mt-6 { - margin-top: 1.5rem; -} - .mb-8 { margin-bottom: 2rem; } @@ -987,11 +1015,6 @@ video { height: 100%; } -.h-min { - height: -webkit-min-content; - height: min-content; -} - .h-8 { height: 2rem; } @@ -1032,14 +1055,14 @@ video { height: 0.75rem; } -.h-11 { - height: 2.75rem; -} - .h-6 { height: 1.5rem; } +.h-11 { + height: 2.75rem; +} + .h-96 { height: 24rem; } @@ -1060,6 +1083,10 @@ video { max-height: 20vh; } +.max-h-8 { + max-height: 2rem; +} + .max-h-32 { max-height: 8rem; } @@ -1068,10 +1095,6 @@ video { max-height: 1.75rem; } -.max-h-8 { - max-height: 2rem; -} - .min-h-\[8rem\] { min-height: 8rem; } @@ -1124,16 +1147,16 @@ video { width: 0.75rem; } -.w-11 { - width: 2.75rem; -} - .w-fit { width: -webkit-fit-content; width: -moz-fit-content; width: fit-content; } +.w-11 { + width: 2.75rem; +} + .w-1\/2 { width: 50%; } @@ -1303,10 +1326,18 @@ video { justify-content: space-between; } +.gap-1 { + gap: 0.25rem; +} + .gap-4 { gap: 1rem; } +.gap-y-1 { + row-gap: 0.25rem; +} + .space-x-2 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(0.5rem * var(--tw-space-x-reverse)); @@ -1443,6 +1474,11 @@ video { border-color: rgb(219 234 254 / var(--tw-border-opacity)); } +.border-gray-400 { + --tw-border-opacity: 1; + border-color: rgb(156 163 175 / var(--tw-border-opacity)); +} + .border-gray-300 { --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); @@ -1458,11 +1494,6 @@ video { border-color: rgb(132 204 22 / var(--tw-border-opacity)); } -.border-gray-400 { - --tw-border-opacity: 1; - border-color: rgb(156 163 175 / var(--tw-border-opacity)); -} - .border-blue-500 { --tw-border-opacity: 1; border-color: rgb(59 130 246 / var(--tw-border-opacity)); @@ -1549,6 +1580,10 @@ video { padding: 0.5rem; } +.p-8 { + padding: 2rem; +} + .p-3 { padding: 0.75rem; } @@ -1593,6 +1628,10 @@ video { padding-left: 1rem; } +.pt-2 { + padding-top: 0.5rem; +} + .pl-2 { padding-left: 0.5rem; } @@ -1617,10 +1656,6 @@ video { padding-right: 0.25rem; } -.pt-2 { - padding-top: 0.5rem; -} - .pb-2 { padding-bottom: 0.5rem; } @@ -1633,6 +1668,10 @@ video { padding-right: 0.5rem; } +.pt-0\.5 { + padding-top: 0.125rem; +} + .pb-8 { padding-bottom: 2rem; } @@ -1653,10 +1692,6 @@ video { padding-right: 0px; } -.pt-0\.5 { - padding-top: 0.125rem; -} - .pb-4 { padding-bottom: 1rem; } @@ -1868,12 +1903,6 @@ video { transition-duration: 150ms; } -.transition-all { - transition-property: all; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} - .transition-\[color\2c background-color\2c box-shadow\] { transition-property: color,background-color,box-shadow; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); @@ -2478,26 +2507,6 @@ li::marker { } } -/**************************************/ - -#topleft-tools { - display: block; - position: absolute; - z-index: 5000; - transition: all 500ms linear; - left: 0; - right: 0; -} - -.welcomeMessage { - display: block; - max-width: calc(100vw - 5em); - width: 40em; - max-height: calc(100vh - 15em); - background-color: var(--background-color); - color: var(--foreground-color); -} - /***************** Info box (box containing features and questions ******************/ input { @@ -2670,6 +2679,10 @@ input { top: 0.75rem; } + .sm\:m-6 { + margin: 1.5rem; + } + .sm\:mx-auto { margin-left: auto; margin-right: auto; @@ -2701,10 +2714,6 @@ input { width: 6rem; } - .sm\:max-w-sm { - max-width: 24rem; - } - .sm\:max-w-xl { max-width: 36rem; } @@ -2725,6 +2734,10 @@ input { border-width: 4px; } + .sm\:p-6 { + padding: 1.5rem; + } + .sm\:p-0\.5 { padding: 0.125rem; } @@ -2782,6 +2795,10 @@ input { margin: 0.25rem; } + .md\:m-8 { + margin: 2rem; + } + .md\:m-2 { margin: 0.5rem; } @@ -2834,10 +2851,6 @@ input { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .md\:flex-row { - flex-direction: row; - } - .md\:rounded-xl { border-radius: 0.75rem; } @@ -2888,10 +2901,6 @@ input { font-size: 1.25rem; line-height: 1.75rem; } - - .md\:w-160 { - width: 40rem; - } } @media (min-width: 1024px) { diff --git a/index.css b/index.css index 4f707d254e..fdaf2101db 100644 --- a/index.css +++ b/index.css @@ -542,25 +542,6 @@ li::marker { } } -/**************************************/ - -#topleft-tools { - display: block; - position: absolute; - z-index: 5000; - transition: all 500ms linear; - left: 0; - right: 0; -} - -.welcomeMessage { - display: block; - max-width: calc(100vw - 5em); - width: 40em; - max-height: calc(100vh - 15em); - background-color: var(--background-color); - color: var(--foreground-color); -} /***************** Info box (box containing features and questions ******************/ diff --git a/index.ts b/index.ts index 3a8fec515e..a012c8cd54 100644 --- a/index.ts +++ b/index.ts @@ -5,7 +5,6 @@ import AllThemesGui from "./UI/AllThemesGui" import DetermineLayout from "./Logic/DetermineLayout" import LayoutConfig from "./Models/ThemeConfig/LayoutConfig" import DefaultGUI from "./UI/DefaultGUI" -import State from "./State" import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation" import { DefaultGuiState } from "./UI/DefaultGuiState" @@ -27,13 +26,7 @@ class Init { } const guiState = new DefaultGuiState() - State.state = new State(layoutToUse) DefaultGuiState.state = guiState - // This 'leaks' the global state via the window object, useful for debugging - // @ts-ignore - window.mapcomplete_state = State.state - - new DefaultGUI(State.state, guiState).setup() } } diff --git a/package-lock.json b/package-lock.json index 2f1747ea64..ba8d50ec5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "license": "GPL-3.0-or-later", "dependencies": { "@onsvisual/svelte-maps": "^1.1.6", + "@rgossiaux/svelte-headlessui": "^1.0.2", + "@rgossiaux/svelte-heroicons": "^0.1.2", "@rollup/plugin-typescript": "^11.0.0", "@turf/boolean-intersects": "^6.5.0", "@turf/buffer": "^6.5.0", @@ -31,10 +33,6 @@ "jest-mock": "^29.4.1", "jspdf": "^2.5.1", "latlon2country": "^1.2.6", - "leaflet": "^1.9.2", - "leaflet-polylineoffset": "^1.1.1", - "leaflet-providers": "^1.13.0", - "leaflet-simple-map-screenshoter": "^0.4.5", "libphonenumber-js": "^1.10.8", "lz-string": "^1.4.4", "mangrove-reviews-typescript": "^1.1.0", @@ -64,8 +62,6 @@ "@tsconfig/svelte": "^3.0.0", "@types/chai": "^4.3.0", "@types/geojson": "^7946.0.10", - "@types/leaflet-markercluster": "^1.0.3", - "@types/leaflet-providers": "^1.2.0", "@types/lz-string": "^1.3.34", "@types/node": "^18.11.18", "@types/papaparse": "^5.3.1", @@ -1912,6 +1908,22 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@rgossiaux/svelte-headlessui": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rgossiaux/svelte-headlessui/-/svelte-headlessui-1.0.2.tgz", + "integrity": "sha512-sauopYTSivhzXe1kAvgawkhyYJcQlK8Li3p0d2OtcCIVprOzdbard5lbqWB4xHDv83zAobt2mR08oizO2poHLQ==", + "peerDependencies": { + "svelte": "^3.44.0" + } + }, + "node_modules/@rgossiaux/svelte-heroicons": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@rgossiaux/svelte-heroicons/-/svelte-heroicons-0.1.2.tgz", + "integrity": "sha512-c5Ep1QDvBo9HD/P0AxbXItDbn6x77fldCjjL0aBjNseUntV4fkdHkBde1IaLr8i0kmrhTSovjkIen8W83jUPzg==", + "peerDependencies": { + "svelte": "^3.44.0" + } + }, "node_modules/@rollup/plugin-json": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.0.0.tgz", @@ -3663,34 +3675,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/leaflet": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.0.tgz", - "integrity": "sha512-7LeOSj7EloC5UcyOMo+1kc3S1UT3MjJxwqsMT1d2PTyvQz53w0Y0oSSk9nwZnOZubCmBvpSNGceucxiq+ZPEUw==", - "dev": true, - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/leaflet-markercluster": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/leaflet-markercluster/-/leaflet-markercluster-1.0.3.tgz", - "integrity": "sha512-GMVrmiFoNdYa0smv+OlnSulhso2BJVnRxNcYAG4l7rC2jCbhp72dvNoLSPnpdJ7MyMScImt9YHFEjLefHO11Uw==", - "deprecated": "'@types/leaflet-markercluster' is now '@types/leaflet.markercluster'.", - "dev": true, - "dependencies": { - "@types/leaflet": "*" - } - }, - "node_modules/@types/leaflet-providers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/leaflet-providers/-/leaflet-providers-1.2.1.tgz", - "integrity": "sha512-uNyuXiNV2q3fmgNjQji2P6RjQISmL40bbOL91/3OAwiE3XhkLKPmSAtAcfe11MAIz45iEjdFZJWppq9QyfnPIw==", - "dev": true, - "dependencies": { - "@types/leaflet": "*" - } - }, "node_modules/@types/lz-string": { "version": "1.3.34", "resolved": "https://registry.npmjs.org/@types/lz-string/-/lz-string-1.3.34.tgz", @@ -5357,11 +5341,6 @@ "doctest-ts-improved": "dist/main.js" } }, - "node_modules/dom-to-image-more": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-2.13.1.tgz", - "integrity": "sha512-ApVHqdGkwSMNcHFoJD/3BNfSTEq0a+GaVU8JNO29n+RZnwOUVtK8zznn4onXHJJlJT63dxQ2n5bwPgENlDZbnA==" - }, "node_modules/dom-walk": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", @@ -5854,11 +5833,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/file-saver": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", - "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" - }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -7284,30 +7258,6 @@ "turf": "^3.0.14" } }, - "node_modules/leaflet": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.3.tgz", - "integrity": "sha512-iB2cR9vAkDOu5l3HAay2obcUHZ7xwUBBjph8+PGtmW/2lYhbLizWtG7nTeYht36WfOslixQF9D/uSIzhZgGMfQ==" - }, - "node_modules/leaflet-polylineoffset": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/leaflet-polylineoffset/-/leaflet-polylineoffset-1.1.1.tgz", - "integrity": "sha512-WcEjAROx9IhIVwSMoFy9p2QBCG9YeuGtJl4ZdunIgj4xbCdTrUkBj8JdonUeCyLPnD2/Vrem/raOPHm5LvebSw==" - }, - "node_modules/leaflet-providers": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/leaflet-providers/-/leaflet-providers-1.13.0.tgz", - "integrity": "sha512-f/sN5wdgBbVA2jcCYzScIfYNxKdn2wBJP9bu+5cRX9Xj6g8Bt1G9Sr8WgJAt/ckIFIc3LVVxCBNFpSCfTuUElg==" - }, - "node_modules/leaflet-simple-map-screenshoter": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/leaflet-simple-map-screenshoter/-/leaflet-simple-map-screenshoter-0.4.5.tgz", - "integrity": "sha512-bvd++mQstpgb7F7c2v5jCLlUitQ6CcYLZ56do7PVyyVpGvnlhIEk5S6dLXqpe3itSr2D/re5yUAOf8zBt95ViQ==", - "dependencies": { - "dom-to-image-more": "^2.8.0", - "file-saver": "^2.0.2" - } - }, "node_modules/levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -13319,6 +13269,18 @@ "integrity": "sha512-bjdcC2t77qBWA499nOZjwCK8tpRX8TgoMMYKWRIIIpL8fsDgG/Myd6FcGasV5RdD3v0im33RAjiZOA2Ybp+S3g==", "dev": true }, + "@rgossiaux/svelte-headlessui": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rgossiaux/svelte-headlessui/-/svelte-headlessui-1.0.2.tgz", + "integrity": "sha512-sauopYTSivhzXe1kAvgawkhyYJcQlK8Li3p0d2OtcCIVprOzdbard5lbqWB4xHDv83zAobt2mR08oizO2poHLQ==", + "requires": {} + }, + "@rgossiaux/svelte-heroicons": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@rgossiaux/svelte-heroicons/-/svelte-heroicons-0.1.2.tgz", + "integrity": "sha512-c5Ep1QDvBo9HD/P0AxbXItDbn6x77fldCjjL0aBjNseUntV4fkdHkBde1IaLr8i0kmrhTSovjkIen8W83jUPzg==", + "requires": {} + }, "@rollup/plugin-json": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.0.0.tgz", @@ -14701,33 +14663,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "@types/leaflet": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.0.tgz", - "integrity": "sha512-7LeOSj7EloC5UcyOMo+1kc3S1UT3MjJxwqsMT1d2PTyvQz53w0Y0oSSk9nwZnOZubCmBvpSNGceucxiq+ZPEUw==", - "dev": true, - "requires": { - "@types/geojson": "*" - } - }, - "@types/leaflet-markercluster": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/leaflet-markercluster/-/leaflet-markercluster-1.0.3.tgz", - "integrity": "sha512-GMVrmiFoNdYa0smv+OlnSulhso2BJVnRxNcYAG4l7rC2jCbhp72dvNoLSPnpdJ7MyMScImt9YHFEjLefHO11Uw==", - "dev": true, - "requires": { - "@types/leaflet": "*" - } - }, - "@types/leaflet-providers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/leaflet-providers/-/leaflet-providers-1.2.1.tgz", - "integrity": "sha512-uNyuXiNV2q3fmgNjQji2P6RjQISmL40bbOL91/3OAwiE3XhkLKPmSAtAcfe11MAIz45iEjdFZJWppq9QyfnPIw==", - "dev": true, - "requires": { - "@types/leaflet": "*" - } - }, "@types/lz-string": { "version": "1.3.34", "resolved": "https://registry.npmjs.org/@types/lz-string/-/lz-string-1.3.34.tgz", @@ -15997,11 +15932,6 @@ "typescript": "^4.6.2" } }, - "dom-to-image-more": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-2.13.1.tgz", - "integrity": "sha512-ApVHqdGkwSMNcHFoJD/3BNfSTEq0a+GaVU8JNO29n+RZnwOUVtK8zznn4onXHJJlJT63dxQ2n5bwPgENlDZbnA==" - }, "dom-walk": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", @@ -16404,11 +16334,6 @@ "escape-string-regexp": "^1.0.5" } }, - "file-saver": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", - "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -17472,30 +17397,6 @@ "turf": "^3.0.14" } }, - "leaflet": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.3.tgz", - "integrity": "sha512-iB2cR9vAkDOu5l3HAay2obcUHZ7xwUBBjph8+PGtmW/2lYhbLizWtG7nTeYht36WfOslixQF9D/uSIzhZgGMfQ==" - }, - "leaflet-polylineoffset": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/leaflet-polylineoffset/-/leaflet-polylineoffset-1.1.1.tgz", - "integrity": "sha512-WcEjAROx9IhIVwSMoFy9p2QBCG9YeuGtJl4ZdunIgj4xbCdTrUkBj8JdonUeCyLPnD2/Vrem/raOPHm5LvebSw==" - }, - "leaflet-providers": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/leaflet-providers/-/leaflet-providers-1.13.0.tgz", - "integrity": "sha512-f/sN5wdgBbVA2jcCYzScIfYNxKdn2wBJP9bu+5cRX9Xj6g8Bt1G9Sr8WgJAt/ckIFIc3LVVxCBNFpSCfTuUElg==" - }, - "leaflet-simple-map-screenshoter": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/leaflet-simple-map-screenshoter/-/leaflet-simple-map-screenshoter-0.4.5.tgz", - "integrity": "sha512-bvd++mQstpgb7F7c2v5jCLlUitQ6CcYLZ56do7PVyyVpGvnlhIEk5S6dLXqpe3itSr2D/re5yUAOf8zBt95ViQ==", - "requires": { - "dom-to-image-more": "^2.8.0", - "file-saver": "^2.0.2" - } - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", diff --git a/package.json b/package.json index b1b3564c41..4f8a4e18f3 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,8 @@ ], "dependencies": { "@onsvisual/svelte-maps": "^1.1.6", + "@rgossiaux/svelte-headlessui": "^1.0.2", + "@rgossiaux/svelte-heroicons": "^0.1.2", "@rollup/plugin-typescript": "^11.0.0", "@turf/boolean-intersects": "^6.5.0", "@turf/buffer": "^6.5.0", diff --git a/scripts/slice.ts b/scripts/slice.ts index a786418543..77b7b65c9a 100644 --- a/scripts/slice.ts +++ b/scripts/slice.ts @@ -1,5 +1,4 @@ import * as fs from "fs" -import TiledFeatureSource from "../Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource" import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" import * as readline from "readline" import ScriptUtils from "./ScriptUtils" @@ -7,6 +6,8 @@ import { Utils } from "../Utils" import Script from "./Script" import { BBox } from "../Logic/BBox" import { GeoOperations } from "../Logic/GeoOperations" +import { Tiles } from "../Models/TileRange" +import { Feature } from "geojson" /** * This script slices a big newline-delimeted geojson file into tiled geojson @@ -91,6 +92,71 @@ class Slice extends Script { } } + private handleTileData( + features: Feature[], + tileIndex: number, + outputDirectory: string, + doSlice: boolean, + handled: number, + maxNumberOfTiles: number + ) { + const [z, x, y] = Tiles.tile_from_index(tileIndex) + const path = `${outputDirectory}/tile_${z}_${x}_${y}.geojson` + const box = BBox.fromTileIndex(tileIndex) + if (doSlice) { + features = Utils.NoNull( + features.map((f) => { + const bbox = box.asGeoJson({}) + const properties = { + ...f.properties, + id: (f.properties?.id ?? "") + "_" + z + "_" + x + "_" + y, + } + + if (GeoOperations.completelyWithin(bbox, f)) { + bbox.properties = properties + return bbox + } + const intersection = GeoOperations.intersect(f, box.asGeoJson({})) + if (intersection) { + intersection.properties = properties + } + return intersection + }) + ) + } + features.forEach((f) => { + delete f.bbox + }) + if (features.length === 0) { + ScriptUtils.erasableLog( + handled + "/" + maxNumberOfTiles, + "Not writing ", + path, + ": no features" + ) + return + } + fs.writeFileSync( + path, + JSON.stringify( + { + type: "FeatureCollection", + features: features, + }, + null, + " " + ) + ) + ScriptUtils.erasableLog( + handled + "/" + maxNumberOfTiles, + "Written ", + path, + "which has ", + features.length, + "features" + ) + } + async main(args: string[]) { console.log("GeoJSON slicer") if (args.length < 3) { @@ -136,75 +202,18 @@ class Slice extends Script { } const maxNumberOfTiles = Math.pow(2, zoomlevel) * Math.pow(2, zoomlevel) let handled = 0 - TiledFeatureSource.createHierarchy(StaticFeatureSource.fromGeojson(allFeatures), { - minZoomLevel: zoomlevel, - maxZoomLevel: zoomlevel, - maxFeatureCount: Number.MAX_VALUE, - registerTile: (tile) => { + StaticFeatureSource.fromGeojson(allFeatures).features.addCallbackAndRun((feats) => { + GeoOperations.slice(zoomlevel, feats).forEach((tileData, tileIndex) => { handled = handled + 1 - const path = `${outputDirectory}/tile_${tile.z}_${tile.x}_${tile.y}.geojson` - const box = BBox.fromTile(tile.z, tile.x, tile.y) - let features = tile.features.data.map((ff) => ff.feature) - if (doSlice) { - features = Utils.NoNull( - features.map((f) => { - const bbox = box.asGeoJson({}) - const properties = { - ...f.properties, - id: - (f.properties?.id ?? "") + - "_" + - tile.z + - "_" + - tile.x + - "_" + - tile.y, - } - - if (GeoOperations.completelyWithin(bbox, f)) { - bbox.properties = properties - return bbox - } - const intersection = GeoOperations.intersect(f, box.asGeoJson({})) - if (intersection) { - intersection.properties = properties - } - return intersection - }) - ) - } - features.forEach((f) => { - delete f.bbox - }) - if (features.length === 0) { - ScriptUtils.erasableLog( - handled + "/" + maxNumberOfTiles, - "Not writing ", - path, - ": no features" - ) - return - } - fs.writeFileSync( - path, - JSON.stringify( - { - type: "FeatureCollection", - features: features, - }, - null, - " " - ) + this.handleTileData( + tileData, + tileIndex, + outputDirectory, + doSlice, + handled, + maxNumberOfTiles ) - ScriptUtils.erasableLog( - handled + "/" + maxNumberOfTiles, - "Written ", - path, - "which has ", - tile.features.data.length, - "features" - ) - }, + }) }) } } diff --git a/test.ts b/test.ts index 73c93eb391..56d594dabd 100644 --- a/test.ts +++ b/test.ts @@ -1,17 +1,17 @@ import SvelteUIElement from "./UI/Base/SvelteUIElement" import ThemeViewGUI from "./UI/ThemeViewGUI.svelte" import { FixedUiElement } from "./UI/Base/FixedUiElement" -import { QueryParameters } from "./Logic/Web/QueryParameters" import LayoutConfig from "./Models/ThemeConfig/LayoutConfig" import * as benches from "./assets/generated/themes/benches.json" async function main() { - new FixedUiElement("Determining layout...").AttachTo("maindiv") - const qp = QueryParameters.GetQueryParameter("layout", "") new FixedUiElement("").AttachTo("extradiv") const layout = new LayoutConfig(benches, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) - console.log("Using layout", layout.id) new SvelteUIElement(ThemeViewGUI, { layout }).AttachTo("maindiv") } +async function test() { + // new LengthInput().AttachTo("maindiv") +} +//test().then((_) => {}) main().then((_) => {}) From 41e6a2c760aefd66011d882bbd9504df81bf10d8 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 29 Mar 2023 17:21:20 +0200 Subject: [PATCH 008/257] More refactoring --- Customizations/AllSharedLayers.ts | 1 - Logic/Actors/TitleHandler.ts | 5 +- .../Actors/SaveFeatureSourceToLocalStorage.ts | 10 +- .../Sources/SimpleFeatureSource.ts | 3 +- .../Sources/SnappingFeatureSource.ts | 52 + .../Sources/TouchesBboxFeatureSource.ts | 1 - Logic/GeoOperations.ts | 32 +- Logic/ImageProviders/AllImageProviders.ts | 3 +- Logic/Osm/Actions/ChangeLocationAction.ts | 2 +- Logic/Osm/Actions/ChangeTagAction.ts | 2 +- Logic/State/UserRelatedState.ts | 1 - Logic/Tags/Tag.ts | 2 +- Models/MapProperties.ts | 2 +- Models/ThemeConfig/LayerConfig.ts | 1 - Models/ThemeViewState.ts | 3 +- UI/AllThemesGui.ts | 6 +- UI/Base/AsyncLazy.ts | 2 +- UI/Base/DivContainer.ts | 1 - UI/Base/DragInvitation.svelte | 89 ++ UI/Base/FromHtml.svelte | 19 + UI/Base/Link.ts | 2 +- UI/Base/Tr.svelte | 30 + UI/BigComponents/ActionButtons.ts | 1 - UI/BigComponents/DownloadPanel.ts | 1 - UI/BigComponents/FullWelcomePaneWithTabs.ts | 1 - UI/BigComponents/Geosearch.svelte | 2 - UI/BigComponents/SelectedElementView.svelte | 43 +- UI/BigComponents/ThemeButton.svelte | 11 +- UI/DefaultGUI.ts | 2 - UI/Image/SlideShow.ts | 2 +- UI/ImportFlow/ConflationChecker.ts | 1 - UI/ImportFlow/FlowStep.ts | 1 - UI/ImportFlow/MapPreview.ts | 1 - UI/Input/DirectionInput.ts | 118 -- UI/Input/FloorLevelInputElement.ts | 2 +- UI/Input/LocationInput.ts | 22 - UI/Input/README.md | 1 + UI/Input/SimpleDatePicker.ts | 1 - UI/Input/TextField.ts | 4 - UI/Input/ValidatedTextField.ts | 1008 ----------------- UI/InputElement/Helpers/DirectionInput.svelte | 70 ++ UI/InputElement/Helpers/LocationInput.svelte | 42 + UI/InputElement/InputHelper.svelte | 13 + UI/InputElement/InputHelpers.ts | 16 + UI/InputElement/ValidatedTextField.ts | 119 ++ UI/InputElement/Validators/ColorValidator.ts | 7 + UI/InputElement/Validators/DateValidator.ts | 23 + .../Validators/DirectionValidator.ts | 17 + UI/InputElement/Validators/EmailValidator.ts | 39 + UI/InputElement/Validators/FloatValidator.ts | 27 + UI/InputElement/Validators/IntValidator.ts | 29 + UI/InputElement/Validators/LengthValidator.ts | 16 + UI/InputElement/Validators/NatValidator.ts | 30 + .../Validators/OpeningHoursValidator.ts | 54 + UI/InputElement/Validators/PFloatValidator.ts | 23 + UI/InputElement/Validators/PNatValidator.ts | 27 + UI/InputElement/Validators/PhoneValidator.ts | 32 + UI/InputElement/Validators/StringValidator.ts | 8 + UI/InputElement/Validators/TextValidator.ts | 7 + UI/InputElement/Validators/UrlValidator.ts | 75 ++ .../Validators/WikidataValidator.ts | 179 +++ UI/Map/MapLibreAdaptor.ts | 2 +- UI/Map/MaplibreMap.svelte | 4 + UI/Map/ShowDataLayer.ts | 4 +- UI/Map/ShowDataLayerOptions.ts | 1 - UI/Popup/HistogramViz.ts | 19 +- UI/Popup/MinimapViz.ts | 3 - UI/Popup/MoveWizard.ts | 5 - UI/Popup/SpecialTranslation.svelte | 34 + UI/Popup/TagRenderingAnswer.svelte | 34 + UI/Popup/TagRenderingAnswer.ts | 4 +- UI/Popup/TagRenderingMapping.svelte | 32 + UI/Popup/TagRenderingQuestion.ts | 1 - UI/Reviews/SingleReview.ts | 1 - UI/SpecialVisualization.ts | 18 +- UI/SpecialVisualizations.ts | 116 +- UI/SubstitutedTranslation.ts | 123 +- UI/ThemeViewGUI.svelte | 61 +- UI/Wikipedia/WikidataPreviewBox.ts | 2 +- UI/i18n/Translation.ts | 8 +- all_themes_index.ts | 24 +- .../bicycle_library/bicycle_library.json | 2 +- .../bicycle_tube_vending_machine.json | 2 +- .../bike_repair_station.json | 2 +- assets/layers/bike_shop/bike_shop.json | 2 +- assets/layers/cafe_pub/cafe_pub.json | 2 +- .../charging_station/charging_station.json | 2 +- assets/layers/climbing/climbing.json | 3 +- .../layers/climbing_club/climbing_club.json | 2 +- assets/layers/climbing_gym/climbing_gym.json | 2 +- assets/layers/conflation/conflation.json | 2 +- assets/layers/current_view/current_view.json | 2 +- assets/layers/filters/filters.json | 2 +- .../layers/fitness_centre/fitness_centre.json | 2 +- .../fitness_station/fitness_station.json | 2 +- assets/layers/ghost_bike/ghost_bike.json | 2 +- assets/layers/gps_location/gps_location.json | 2 +- .../gps_location_history.json | 2 +- assets/layers/gps_track/gps_track.json | 2 +- .../layers/home_location/home_location.json | 2 +- assets/layers/hospital/hospital.json | 2 +- assets/layers/icons/icons.json | 4 +- assets/layers/id_presets/id_presets.json | 2 +- .../import_candidate/import_candidate.json | 4 +- assets/layers/matchpoint/matchpoint.json | 4 +- .../layers/parcel_lockers/parcel_lockers.json | 2 +- assets/layers/pharmacy/pharmacy.json | 2 +- assets/layers/postoffices/postoffices.json | 2 +- assets/layers/range/range.json | 2 +- assets/layers/school/school.json | 2 +- assets/layers/shops/shops.json | 2 +- assets/layers/split_point/split_point.json | 2 +- assets/layers/sport_pitch/sport_pitch.json | 2 +- .../layers/sports_centre/sports_centre.json | 2 +- assets/layers/toilet/toilet.json | 2 +- .../toilet_at_amenity/toilet_at_amenity.json | 2 +- assets/layers/usersettings/usersettings.json | 2 +- assets/layers/veterinary/veterinary.json | 2 +- assets/themes/buurtnatuur/buurtnatuur.json | 2 +- assets/themes/grb/grb.json | 2 +- assets/themes/speelplekken/speelplekken.json | 2 +- css/index-tailwind-output.css | 184 +-- index.css | 103 -- index.html | 16 +- index.ts | 1 - langs/en.json | 6 +- langs/layers/ca.json | 3 - langs/layers/de.json | 3 - langs/layers/en.json | 3 - langs/layers/nl.json | 12 - langs/themes/ca.json | 2 +- langs/themes/cs.json | 4 +- langs/themes/da.json | 4 +- langs/themes/de.json | 4 +- langs/themes/en.json | 4 +- langs/themes/es.json | 4 +- langs/themes/fr.json | 4 +- langs/themes/nl.json | 8 +- package-lock.json | 11 + package.json | 1 + public/maplibre-gl.css | 1 + scripts/generateContributors.ts | 2 +- scripts/generateStats.ts | 1 - scripts/generateTaginfoProjectFiles.ts | 2 - test.ts | 24 +- test/Logic/Actors/Actors.spec.ts | 2 +- test/UI/Popup/TagRenderingQuestion.spec.ts | 1 - 147 files changed, 1540 insertions(+), 1797 deletions(-) create mode 100644 Logic/FeatureSource/Sources/SnappingFeatureSource.ts create mode 100644 UI/Base/DragInvitation.svelte create mode 100644 UI/Base/FromHtml.svelte create mode 100644 UI/Base/Tr.svelte delete mode 100644 UI/Input/DirectionInput.ts create mode 100644 UI/Input/README.md delete mode 100644 UI/Input/ValidatedTextField.ts create mode 100644 UI/InputElement/Helpers/DirectionInput.svelte create mode 100644 UI/InputElement/Helpers/LocationInput.svelte create mode 100644 UI/InputElement/InputHelper.svelte create mode 100644 UI/InputElement/InputHelpers.ts create mode 100644 UI/InputElement/ValidatedTextField.ts create mode 100644 UI/InputElement/Validators/ColorValidator.ts create mode 100644 UI/InputElement/Validators/DateValidator.ts create mode 100644 UI/InputElement/Validators/DirectionValidator.ts create mode 100644 UI/InputElement/Validators/EmailValidator.ts create mode 100644 UI/InputElement/Validators/FloatValidator.ts create mode 100644 UI/InputElement/Validators/IntValidator.ts create mode 100644 UI/InputElement/Validators/LengthValidator.ts create mode 100644 UI/InputElement/Validators/NatValidator.ts create mode 100644 UI/InputElement/Validators/OpeningHoursValidator.ts create mode 100644 UI/InputElement/Validators/PFloatValidator.ts create mode 100644 UI/InputElement/Validators/PNatValidator.ts create mode 100644 UI/InputElement/Validators/PhoneValidator.ts create mode 100644 UI/InputElement/Validators/StringValidator.ts create mode 100644 UI/InputElement/Validators/TextValidator.ts create mode 100644 UI/InputElement/Validators/UrlValidator.ts create mode 100644 UI/InputElement/Validators/WikidataValidator.ts create mode 100644 UI/Popup/SpecialTranslation.svelte create mode 100644 UI/Popup/TagRenderingMapping.svelte create mode 100644 public/maplibre-gl.css diff --git a/Customizations/AllSharedLayers.ts b/Customizations/AllSharedLayers.ts index 7e4cf71a19..43c18e9816 100644 --- a/Customizations/AllSharedLayers.ts +++ b/Customizations/AllSharedLayers.ts @@ -2,7 +2,6 @@ import LayerConfig from "../Models/ThemeConfig/LayerConfig" import { Utils } from "../Utils" import known_themes from "../assets/generated/known_layers.json" import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson" -import { ALL } from "dns" import { AllKnownLayouts } from "./AllKnownLayouts" export class AllSharedLayers { public static sharedLayers: Map = AllSharedLayers.getSharedLayers() diff --git a/Logic/Actors/TitleHandler.ts b/Logic/Actors/TitleHandler.ts index f2228fa456..d8af466c80 100644 --- a/Logic/Actors/TitleHandler.ts +++ b/Logic/Actors/TitleHandler.ts @@ -1,12 +1,13 @@ import { Store, UIEventSource } from "../UIEventSource" import Locale from "../../UI/i18n/Locale" -import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer" import Combine from "../../UI/Base/Combine" import { Utils } from "../../Utils" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { Feature } from "geojson" import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" +import SvelteUIElement from "../../UI/Base/SvelteUIElement" +import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer.svelte" export default class TitleHandler { constructor( @@ -32,7 +33,7 @@ export default class TitleHandler { const tagsSource = allElements.getStore(tags.id) ?? new UIEventSource>(tags) - const title = new TagRenderingAnswer(tagsSource, layer.title, {}) + const title = new SvelteUIElement(TagRenderingAnswer, { tags: tagsSource }) return ( new Combine([defaultTitle, " | ", title]).ConstructElement() ?.textContent ?? defaultTitle diff --git a/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts b/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts index 29519b6bbf..5e113aaff0 100644 --- a/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts +++ b/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts @@ -1,12 +1,4 @@ -import FeatureSource, { Tiled } from "../FeatureSource" -import { Tiles } from "../../../Models/TileRange" -import { IdbLocalStorage } from "../../Web/IdbLocalStorage" -import { UIEventSource } from "../../UIEventSource" -import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" -import { BBox } from "../../BBox" -import SimpleFeatureSource from "../Sources/SimpleFeatureSource" -import FilteredLayer from "../../../Models/FilteredLayer" -import Loc from "../../../Models/Loc" +import FeatureSource from "../FeatureSource" import { Feature } from "geojson" import TileLocalStorage from "./TileLocalStorage" import { GeoOperations } from "../../GeoOperations" diff --git a/Logic/FeatureSource/Sources/SimpleFeatureSource.ts b/Logic/FeatureSource/Sources/SimpleFeatureSource.ts index 543a26ad5c..c280bebcbd 100644 --- a/Logic/FeatureSource/Sources/SimpleFeatureSource.ts +++ b/Logic/FeatureSource/Sources/SimpleFeatureSource.ts @@ -1,7 +1,6 @@ import { UIEventSource } from "../../UIEventSource" import FilteredLayer from "../../../Models/FilteredLayer" -import { FeatureSourceForLayer, Tiled } from "../FeatureSource" -import { BBox } from "../../BBox" +import { FeatureSourceForLayer } from "../FeatureSource" import { Feature } from "geojson" export default class SimpleFeatureSource implements FeatureSourceForLayer { diff --git a/Logic/FeatureSource/Sources/SnappingFeatureSource.ts b/Logic/FeatureSource/Sources/SnappingFeatureSource.ts new file mode 100644 index 0000000000..855e935ea7 --- /dev/null +++ b/Logic/FeatureSource/Sources/SnappingFeatureSource.ts @@ -0,0 +1,52 @@ +import FeatureSource from "../FeatureSource" +import { Store } from "../../UIEventSource" +import { Feature, Point } from "geojson" +import { GeoOperations } from "../../GeoOperations" + +export interface SnappingOptions { + /** + * If the distance is bigger then this amount, don't snap. + * In meter + */ + maxDistance?: number +} + +export default class SnappingFeatureSource implements FeatureSource { + public readonly features: Store[]> + + constructor( + snapTo: FeatureSource, + location: Store<{ lon: number; lat: number }>, + options?: SnappingOptions + ) { + const simplifiedFeatures = snapTo.features.mapD((features) => + features + .filter((feature) => feature.geometry.type !== "Point") + .map((f) => GeoOperations.forceLineString(f)) + ) + + location.mapD( + ({ lon, lat }) => { + const features = snapTo.features.data + const loc: [number, number] = [lon, lat] + const maxDistance = (options?.maxDistance ?? 1000) * 1000 + let bestSnap: Feature = undefined + for (const feature of features) { + const snapped = GeoOperations.nearestPoint(feature, loc) + if (snapped.properties.dist > maxDistance) { + continue + } + if ( + bestSnap === undefined || + bestSnap.properties.dist > snapped.properties.dist + ) { + snapped.properties["snapped-to"] = feature.properties.id + bestSnap = snapped + } + } + return bestSnap + }, + [snapTo.features] + ) + } +} diff --git a/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts b/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts index 2fb3fd0a2f..6384a3cfd1 100644 --- a/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts +++ b/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts @@ -2,7 +2,6 @@ import FeatureSource, { FeatureSourceForLayer } from "../FeatureSource" import StaticFeatureSource from "./StaticFeatureSource" import { GeoOperations } from "../../GeoOperations" import { BBox } from "../../BBox" -import exp from "constants" import FilteredLayer from "../../../Models/FilteredLayer" /** diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 302e809a38..3b5df2addf 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -7,6 +7,7 @@ import { GeoJSON, Geometry, LineString, + MultiLineString, MultiPolygon, Point, Polygon, @@ -272,17 +273,42 @@ export class GeoOperations { * @param point Point defined as [lon, lat] */ public static nearestPoint( - way: Feature, + way: Feature, point: [number, number] - ): Feature { + ): Feature< + Point, + { + index: number + dist: number + location: number + } + > { + return ( + turf.nearestPointOnLine(>way, point, { units: "kilometers" }) + ) + } + + /** + * Helper method to reuse the coordinates of the way as LineString. + * Mostly used as helper for 'nearestPoint' + * @param way + */ + public static forceLineString( + way: Feature + ): Feature { if (way.geometry.type === "Polygon") { way = { ...way } way.geometry = { ...way.geometry } way.geometry.type = "LineString" way.geometry.coordinates = (way.geometry).coordinates[0] + } else if (way.geometry.type === "MultiPolygon") { + way = { ...way } + way.geometry = { ...way.geometry } + way.geometry.type = "MultiLineString" + way.geometry.coordinates = (way.geometry).coordinates[0] } - return turf.nearestPointOnLine(>way, point, { units: "kilometers" }) + return way } public static toCSV(features: any[]): string { diff --git a/Logic/ImageProviders/AllImageProviders.ts b/Logic/ImageProviders/AllImageProviders.ts index 770da2fbf1..6099d0ee44 100644 --- a/Logic/ImageProviders/AllImageProviders.ts +++ b/Logic/ImageProviders/AllImageProviders.ts @@ -5,6 +5,7 @@ import GenericImageProvider from "./GenericImageProvider" import { Store, UIEventSource } from "../UIEventSource" import ImageProvider, { ProvidedImage } from "./ImageProvider" import { WikidataImageProvider } from "./WikidataImageProvider" +import { OsmTags } from "../../Models/OsmFeature" /** * A generic 'from the interwebz' image picker, without attribution @@ -44,7 +45,7 @@ export default class AllImageProviders { UIEventSource >() - public static LoadImagesFor(tags: Store, tagKey?: string[]): Store { + public static LoadImagesFor(tags: Store, tagKey?: string[]): Store { if (tags.data.id === undefined) { return undefined } diff --git a/Logic/Osm/Actions/ChangeLocationAction.ts b/Logic/Osm/Actions/ChangeLocationAction.ts index 0ec12bc7ba..a0bcf97dbf 100644 --- a/Logic/Osm/Actions/ChangeLocationAction.ts +++ b/Logic/Osm/Actions/ChangeLocationAction.ts @@ -24,7 +24,7 @@ export default class ChangeLocationAction extends OsmChangeAction { this._meta = meta } - protected async CreateChangeDescriptions(changes: Changes): Promise { + protected async CreateChangeDescriptions(): Promise { const d: ChangeDescription = { changes: { lat: this._newLonLat[1], diff --git a/Logic/Osm/Actions/ChangeTagAction.ts b/Logic/Osm/Actions/ChangeTagAction.ts index 63d8c23209..2e28a788b8 100644 --- a/Logic/Osm/Actions/ChangeTagAction.ts +++ b/Logic/Osm/Actions/ChangeTagAction.ts @@ -71,7 +71,7 @@ export default class ChangeTagAction extends OsmChangeAction { return { k: key.trim(), v: value.trim() } } - async CreateChangeDescriptions(changes: Changes): Promise { + async CreateChangeDescriptions(): Promise { const changedTags: { k: string; v: string }[] = this._tagsFilter .asChange(this._currentTags) .map(ChangeTagAction.checkChange) diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index 8f80a5046e..06996e9e5f 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -3,7 +3,6 @@ import { OsmConnection } from "../Osm/OsmConnection" import { MangroveIdentity } from "../Web/MangroveReviews" import { Store, Stores, UIEventSource } from "../UIEventSource" import Locale from "../../UI/i18n/Locale" -import { Changes } from "../Osm/Changes" import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" import FeatureSource from "../FeatureSource/FeatureSource" import { Feature } from "geojson" diff --git a/Logic/Tags/Tag.ts b/Logic/Tags/Tag.ts index 945bd49b2d..bab74dc6db 100644 --- a/Logic/Tags/Tag.ts +++ b/Logic/Tags/Tag.ts @@ -122,7 +122,7 @@ export class Tag extends TagsFilter { return [this] } - asChange(properties: any): { k: string; v: string }[] { + asChange(): { k: string; v: string }[] { return [{ k: this.key, v: this.value }] } diff --git a/Models/MapProperties.ts b/Models/MapProperties.ts index cbb34e7f3d..39a49adccf 100644 --- a/Models/MapProperties.ts +++ b/Models/MapProperties.ts @@ -1,4 +1,4 @@ -import { Store, UIEventSource } from "../Logic/UIEventSource" +import { UIEventSource } from "../Logic/UIEventSource" import { BBox } from "../Logic/BBox" import { RasterLayerPolygon } from "./RasterLayers" diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index db15a5df4c..05fc634bf7 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -26,7 +26,6 @@ import Table from "../../UI/Base/Table" import FilterConfigJson from "./Json/FilterConfigJson" import { And } from "../../Logic/Tags/And" import { Overpass } from "../../Logic/Osm/Overpass" -import Constants from "../Constants" import { FixedUiElement } from "../../UI/Base/FixedUiElement" import Svg from "../../Svg" import { ImmutableStore } from "../../Logic/UIEventSource" diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index f2f36e34f2..f175a6e94e 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -108,7 +108,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location) this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id) - const indexedElements = new LayoutSource( + this.indexedFeatures = new LayoutSource( layout.layers, this.featureSwitches, new StaticFeatureSource([]), @@ -116,6 +116,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.osmConnection.Backend(), (id) => this.layerState.filteredLayers.get(id).isDisplayed ) + const indexedElements = this.indexedFeatures this.featureProperties = new FeaturePropertiesStore(indexedElements) const perLayer = new PerLayerFeatureSourceSplitter( Array.from(this.layerState.filteredLayers.values()), diff --git a/UI/AllThemesGui.ts b/UI/AllThemesGui.ts index c55e188c04..605f78fcc1 100644 --- a/UI/AllThemesGui.ts +++ b/UI/AllThemesGui.ts @@ -15,7 +15,6 @@ import { OsmConnection } from "../Logic/Osm/OsmConnection" export default class AllThemesGui { setup() { try { - new FixedUiElement("").AttachTo("centermessage") const osmConnection = new OsmConnection() const state = new UserRelatedState(osmConnection) const intro = new Combine([ @@ -38,15 +37,14 @@ export default class AllThemesGui { new FixedUiElement("v" + Constants.vNumber), ]) .SetClass("block m-5 lg:w-3/4 lg:ml-40") - .SetStyle("pointer-events: all;") - .AttachTo("top-left") + .AttachTo("main") } catch (e) { console.error(">>>> CRITICAL", e) new FixedUiElement( "Seems like no layers are compiled - check the output of `npm run generate:layeroverview`. Is this visible online? Contact pietervdvn immediately!" ) .SetClass("alert") - .AttachTo("centermessage") + .AttachTo("main") } } } diff --git a/UI/Base/AsyncLazy.ts b/UI/Base/AsyncLazy.ts index d09c9769f0..71c6ab34ea 100644 --- a/UI/Base/AsyncLazy.ts +++ b/UI/Base/AsyncLazy.ts @@ -1,6 +1,6 @@ import BaseUIElement from "../BaseUIElement" import { VariableUiElement } from "./VariableUIElement" -import { Stores, UIEventSource } from "../../Logic/UIEventSource" +import { Stores } from "../../Logic/UIEventSource" import Loading from "./Loading" export default class AsyncLazy extends BaseUIElement { diff --git a/UI/Base/DivContainer.ts b/UI/Base/DivContainer.ts index bc6c9e68e5..f36b041d62 100644 --- a/UI/Base/DivContainer.ts +++ b/UI/Base/DivContainer.ts @@ -1,4 +1,3 @@ -import { UIElement } from "../UIElement" import BaseUIElement from "../BaseUIElement" /** diff --git a/UI/Base/DragInvitation.svelte b/UI/Base/DragInvitation.svelte new file mode 100644 index 0000000000..853a0ca06b --- /dev/null +++ b/UI/Base/DragInvitation.svelte @@ -0,0 +1,89 @@ + + + +
+
+ +
+
+ + diff --git a/UI/Base/FromHtml.svelte b/UI/Base/FromHtml.svelte new file mode 100644 index 0000000000..af07db5459 --- /dev/null +++ b/UI/Base/FromHtml.svelte @@ -0,0 +1,19 @@ + + +{#if src !== undefined} + +{/if} + diff --git a/UI/Base/Link.ts b/UI/Base/Link.ts index 76c025dc61..25f107a479 100644 --- a/UI/Base/Link.ts +++ b/UI/Base/Link.ts @@ -1,6 +1,6 @@ import Translations from "../i18n/Translations" import BaseUIElement from "../BaseUIElement" -import { Store, UIEventSource } from "../../Logic/UIEventSource" +import { Store } from "../../Logic/UIEventSource" export default class Link extends BaseUIElement { private readonly _href: string | Store diff --git a/UI/Base/Tr.svelte b/UI/Base/Tr.svelte new file mode 100644 index 0000000000..53e92bbae8 --- /dev/null +++ b/UI/Base/Tr.svelte @@ -0,0 +1,30 @@ + + + diff --git a/UI/BigComponents/ActionButtons.ts b/UI/BigComponents/ActionButtons.ts index fad2a02240..beba679860 100644 --- a/UI/BigComponents/ActionButtons.ts +++ b/UI/BigComponents/ActionButtons.ts @@ -13,7 +13,6 @@ import { OpenIdEditor, OpenJosm } from "./CopyrightPanel" import Toggle from "../Input/Toggle" import ScrollableFullScreen from "../Base/ScrollableFullScreen" import { DefaultGuiState } from "../DefaultGuiState" -import DefaultGUI from "../DefaultGUI" export class BackToThemeOverview extends Toggle { constructor( diff --git a/UI/BigComponents/DownloadPanel.ts b/UI/BigComponents/DownloadPanel.ts index 98ffad07e4..a47703ba71 100644 --- a/UI/BigComponents/DownloadPanel.ts +++ b/UI/BigComponents/DownloadPanel.ts @@ -14,7 +14,6 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { BBox } from "../../Logic/BBox" import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" import geojson2svg from "geojson2svg" -import Constants from "../../Models/Constants" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" export class DownloadPanel extends Toggle { diff --git a/UI/BigComponents/FullWelcomePaneWithTabs.ts b/UI/BigComponents/FullWelcomePaneWithTabs.ts index 5637a05d15..6318595c07 100644 --- a/UI/BigComponents/FullWelcomePaneWithTabs.ts +++ b/UI/BigComponents/FullWelcomePaneWithTabs.ts @@ -17,7 +17,6 @@ import UserRelatedState from "../../Logic/State/UserRelatedState" import Loc from "../../Models/Loc" import BaseLayer from "../../Models/BaseLayer" import FilteredLayer from "../../Models/FilteredLayer" -import CopyrightPanel from "./CopyrightPanel" import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" import PrivacyPolicy from "./PrivacyPolicy" import Hotkeys from "../Base/Hotkeys" diff --git a/UI/BigComponents/Geosearch.svelte b/UI/BigComponents/Geosearch.svelte index fe40cb7c73..bc1f30f306 100644 --- a/UI/BigComponents/Geosearch.svelte +++ b/UI/BigComponents/Geosearch.svelte @@ -15,8 +15,6 @@ Translations.t; export let bounds: UIEventSource - export let layout: LayoutConfig; - export let perLayer: ReadonlyMap export let selectedElement: UIEventSource; export let selectedLayer: UIEventSource; diff --git a/UI/BigComponents/SelectedElementView.svelte b/UI/BigComponents/SelectedElementView.svelte index 5c6715326f..ee380537f2 100644 --- a/UI/BigComponents/SelectedElementView.svelte +++ b/UI/BigComponents/SelectedElementView.svelte @@ -1,23 +1,15 @@
-
selectedElement.setData(undefined)}>close
- new VariableUiElement(tags.mapD(tags => new TagRenderingAnswer(tags, layer.data.title, specialVisState), [layer]))}> +

+ +

- - {#each $layer.titleIcons as titleIconConfig (titleIconConfig.id)} + {#each layer.titleIcons as titleIconConfig (titleIconConfig.id)}
- new VariableUiElement(tags.mapD(tags => new TagRenderingAnswer(tags, titleIconConfig, specialVisState)))}> +
- {/each}
-
    - - {#each Object.keys($_tags) as key} -
  • {key}={$_tags[key]}
  • +
    + {#each layer.tagRenderings as config (config.id)} + {/each} -
+
+
diff --git a/UI/BigComponents/ThemeButton.svelte b/UI/BigComponents/ThemeButton.svelte index 556ba4fc62..2b3814ec07 100644 --- a/UI/BigComponents/ThemeButton.svelte +++ b/UI/BigComponents/ThemeButton.svelte @@ -7,6 +7,7 @@ import Constants from "../../Models/Constants" import type Loc from "../../Models/Loc" import type { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"; + import Tr from "../Base/Tr.svelte"; export let theme: LayoutInformation export let isCustom: boolean = false @@ -16,8 +17,8 @@ $: title = new Translation( theme.title, !isCustom && !theme.mustHaveLanguage ? "themes:" + theme.id + ".title" : undefined - ).toString() - $: description = new Translation(theme.shortDescription).toString() + ) + $: description = new Translation(theme.shortDescription) // TODO: Improve this function function createUrl( @@ -83,8 +84,10 @@ - {title} - {description} + + + + diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index af3cc05dc0..5de8eb26b2 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -5,7 +5,6 @@ import FullWelcomePaneWithTabs from "./BigComponents/FullWelcomePaneWithTabs" import MapControlButton from "./MapControlButton" import Svg from "../Svg" import Toggle from "./Input/Toggle" -import SearchAndGo from "./BigComponents/SearchAndGo" import BaseUIElement from "./BaseUIElement" import LeftControls from "./BigComponents/LeftControls" import RightControls from "./BigComponents/RightControls" @@ -26,7 +25,6 @@ import UserInformationPanel from "./BigComponents/UserInformation" import { LoginToggle } from "./Popup/LoginButton" import { FixedUiElement } from "./Base/FixedUiElement" import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" -import { GeoLocationState } from "../Logic/State/GeoLocationState" import Hotkeys from "./Base/Hotkeys" import CopyrightPanel from "./BigComponents/CopyrightPanel" import SvelteUIElement from "./Base/SvelteUIElement" diff --git a/UI/Image/SlideShow.ts b/UI/Image/SlideShow.ts index 51bc6edb60..ee07cb2089 100644 --- a/UI/Image/SlideShow.ts +++ b/UI/Image/SlideShow.ts @@ -1,4 +1,4 @@ -import { Store, UIEventSource } from "../../Logic/UIEventSource" +import { Store } from "../../Logic/UIEventSource" import BaseUIElement from "../BaseUIElement" import { Utils } from "../../Utils" import Combine from "../Base/Combine" diff --git a/UI/ImportFlow/ConflationChecker.ts b/UI/ImportFlow/ConflationChecker.ts index 70ba96c4de..c1ab25f13d 100644 --- a/UI/ImportFlow/ConflationChecker.ts +++ b/UI/ImportFlow/ConflationChecker.ts @@ -17,7 +17,6 @@ import Minimap from "../Base/Minimap" import BaseLayer from "../../Models/BaseLayer" import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" import Loc from "../../Models/Loc" -import Attribution from "../BigComponents/Attribution" import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import ValidatedTextField from "../Input/ValidatedTextField" diff --git a/UI/ImportFlow/FlowStep.ts b/UI/ImportFlow/FlowStep.ts index b99b2829ec..1da6fdce99 100644 --- a/UI/ImportFlow/FlowStep.ts +++ b/UI/ImportFlow/FlowStep.ts @@ -7,7 +7,6 @@ import Translations from "../i18n/Translations" import { VariableUiElement } from "../Base/VariableUIElement" import Toggle from "../Input/Toggle" import { UIElement } from "../UIElement" -import { FixedUiElement } from "../Base/FixedUiElement" export interface FlowStep extends BaseUIElement { readonly IsValid: Store diff --git a/UI/ImportFlow/MapPreview.ts b/UI/ImportFlow/MapPreview.ts index ce8c2ae513..56d9736bb9 100644 --- a/UI/ImportFlow/MapPreview.ts +++ b/UI/ImportFlow/MapPreview.ts @@ -4,7 +4,6 @@ import { BBox } from "../../Logic/BBox" import UserRelatedState from "../../Logic/State/UserRelatedState" import Translations from "../i18n/Translations" import { AllKnownLayouts } from "../../Customizations/AllKnownLayouts" -import Constants from "../../Models/Constants" import { DropDown } from "../Input/DropDown" import { Utils } from "../../Utils" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" diff --git a/UI/Input/DirectionInput.ts b/UI/Input/DirectionInput.ts deleted file mode 100644 index 2df6adf238..0000000000 --- a/UI/Input/DirectionInput.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { InputElement } from "./InputElement" -import { UIEventSource } from "../../Logic/UIEventSource" -import Combine from "../Base/Combine" -import Svg from "../../Svg" -import BaseUIElement from "../BaseUIElement" -import { FixedUiElement } from "../Base/FixedUiElement" -import { Utils } from "../../Utils" -import Loc from "../../Models/Loc" -import Minimap from "../Base/Minimap" - -/** - * Selects a direction in degrees - */ -export default class DirectionInput extends InputElement { - public readonly IsSelected: UIEventSource = new UIEventSource(false) - private readonly _location: UIEventSource - private readonly value: UIEventSource - private background - - constructor( - mapBackground: UIEventSource, - location: UIEventSource, - value?: UIEventSource - ) { - super() - this._location = location - this.value = value ?? new UIEventSource(undefined) - this.background = mapBackground - } - - GetValue(): UIEventSource { - return this.value - } - - IsValid(str: string): boolean { - const t = Number(str) - return !isNaN(t) && t >= 0 && t <= 360 - } - - protected InnerConstructElement(): HTMLElement { - let map: BaseUIElement = new FixedUiElement("") - if (!Utils.runningFromConsole) { - map = Minimap.createMiniMap({ - background: this.background, - allowMoving: false, - location: this._location, - }) - } - - const element = new Combine([ - Svg.direction_stroke_svg() - .SetStyle( - `position: absolute;top: 0;left: 0;width: 100%;height: 100%;transform:rotate(${ - this.value.data ?? 0 - }deg);` - ) - .SetClass("direction-svg relative") - .SetStyle("z-index: 1000"), - map.SetStyle(`position: absolute;top: 0;left: 0;width: 100%;height: 100%;`), - ]) - .SetStyle("width: min(100%, 25em); height: 0; padding-bottom: 100%") // A bit a weird CSS , see https://stackoverflow.com/questions/13851940/pure-css-solution-square-elements#19448481 - .SetClass("relative block bg-white border border-black overflow-hidden rounded-full") - .ConstructElement() - - this.value.addCallbackAndRunD((rotation) => { - const cone = element.getElementsByClassName("direction-svg")[0] as HTMLElement - cone.style.transform = `rotate(${rotation}deg)` - }) - - this.RegisterTriggers(element) - element.style.overflow = "hidden" - element.style.display = "block" - - return element - } - - private RegisterTriggers(htmlElement: HTMLElement) { - const self = this - - function onPosChange(x: number, y: number) { - const rect = htmlElement.getBoundingClientRect() - const dx = -(rect.left + rect.right) / 2 + x - const dy = (rect.top + rect.bottom) / 2 - y - const angle = (180 * Math.atan2(dy, dx)) / Math.PI - const angleGeo = Math.floor((450 - angle) % 360) - self.value.setData("" + angleGeo) - } - - htmlElement.ontouchmove = (ev: TouchEvent) => { - onPosChange(ev.touches[0].clientX, ev.touches[0].clientY) - ev.preventDefault() - } - - htmlElement.ontouchstart = (ev: TouchEvent) => { - onPosChange(ev.touches[0].clientX, ev.touches[0].clientY) - } - - let isDown = false - - htmlElement.onmousedown = (ev: MouseEvent) => { - isDown = true - onPosChange(ev.clientX, ev.clientY) - ev.preventDefault() - } - - htmlElement.onmouseup = (ev) => { - isDown = false - ev.preventDefault() - } - - htmlElement.onmousemove = (ev: MouseEvent) => { - if (isDown) { - onPosChange(ev.clientX, ev.clientY) - } - ev.preventDefault() - } - } -} diff --git a/UI/Input/FloorLevelInputElement.ts b/UI/Input/FloorLevelInputElement.ts index 8b01b77838..6d88eb6d93 100644 --- a/UI/Input/FloorLevelInputElement.ts +++ b/UI/Input/FloorLevelInputElement.ts @@ -1,5 +1,5 @@ import { InputElement } from "./InputElement" -import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource" +import { Store, UIEventSource } from "../../Logic/UIEventSource" import Combine from "../Base/Combine" import Slider from "./Slider" import { ClickableToggle } from "./Toggle" diff --git a/UI/Input/LocationInput.ts b/UI/Input/LocationInput.ts index 38518392cd..209b1f986b 100644 --- a/UI/Input/LocationInput.ts +++ b/UI/Input/LocationInput.ts @@ -1,24 +1,17 @@ import { ReadonlyInputElement } from "./InputElement" import Loc from "../../Models/Loc" import { Store, UIEventSource } from "../../Logic/UIEventSource" -import Minimap, { MinimapObj } from "../Base/Minimap" -import BaseLayer from "../../Models/BaseLayer" import Combine from "../Base/Combine" import Svg from "../../Svg" import { GeoOperations } from "../../Logic/GeoOperations" -import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { BBox } from "../../Logic/BBox" import { FixedUiElement } from "../Base/FixedUiElement" -import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" import BaseUIElement from "../BaseUIElement" -import Toggle from "./Toggle" import matchpoint from "../../assets/layers/matchpoint/matchpoint.json" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import FilteredLayer from "../../Models/FilteredLayer" -import { ElementStorage } from "../../Logic/ElementStorage" -import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" import { RelationId, WayId } from "../../Models/OsmFeature" import { Feature, LineString, Polygon } from "geojson" import { OsmObject, OsmWay } from "../../Logic/Osm/OsmObject" @@ -313,10 +306,6 @@ export default class LocationInput [this.map.leafletMap] ) - const animatedHand = Svg.hand_ui() - .SetStyle("width: 2rem; height: unset;") - .SetClass("hand-drag-animation block pointer-events-none") - return new Combine([ new Combine([ Svg.move_arrows_ui() @@ -328,10 +317,6 @@ export default class LocationInput "background: rgba(255, 128, 128, 0.21); left: 50%; top: 50%; opacity: 0.5" ), - new Toggle(undefined, animatedHand, hasMoved) - .SetClass("block w-0 h-0 z-10 relative") - .SetStyle("left: calc(50% + 3rem); top: calc(50% + 2rem); opacity: 0.7"), - this.map.SetClass("z-0 relative block w-full h-full bg-gray-100"), ]).ConstructElement() } catch (e) { @@ -341,11 +326,4 @@ export default class LocationInput .ConstructElement() } } - - TakeScreenshot(format: "image"): Promise - TakeScreenshot(format: "blob"): Promise - TakeScreenshot(format: "image" | "blob"): Promise - TakeScreenshot(format: "image" | "blob"): Promise { - return this.map.TakeScreenshot(format) - } } diff --git a/UI/Input/README.md b/UI/Input/README.md new file mode 100644 index 0000000000..459e6c8565 --- /dev/null +++ b/UI/Input/README.md @@ -0,0 +1 @@ +This is the old, deprecated directory. New, SVelte-based items go into `InputElement` diff --git a/UI/Input/SimpleDatePicker.ts b/UI/Input/SimpleDatePicker.ts index 2c40242b91..8600830821 100644 --- a/UI/Input/SimpleDatePicker.ts +++ b/UI/Input/SimpleDatePicker.ts @@ -2,7 +2,6 @@ import { InputElement } from "./InputElement" import { UIEventSource } from "../../Logic/UIEventSource" export default class SimpleDatePicker extends InputElement { - IsSelected: UIEventSource = new UIEventSource(false) private readonly value: UIEventSource private readonly _element: HTMLElement diff --git a/UI/Input/TextField.ts b/UI/Input/TextField.ts index 291906c2f1..7b7dc5bd57 100644 --- a/UI/Input/TextField.ts +++ b/UI/Input/TextField.ts @@ -50,10 +50,6 @@ export class TextField extends InputElement { return this.value } - GetRawValue(): UIEventSource { - return this._rawValue - } - IsValid(t: string): boolean { if (t === undefined || t === null) { return false diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts deleted file mode 100644 index 4cc8864f6e..0000000000 --- a/UI/Input/ValidatedTextField.ts +++ /dev/null @@ -1,1008 +0,0 @@ -import { DropDown } from "./DropDown" -import * as EmailValidator from "email-validator" -import { parsePhoneNumberFromString } from "libphonenumber-js" -import { InputElement } from "./InputElement" -import { TextField } from "./TextField" -import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" -import CombinedInputElement from "./CombinedInputElement" -import SimpleDatePicker from "./SimpleDatePicker" -import OpeningHoursInput from "../OpeningHours/OpeningHoursInput" -import DirectionInput from "./DirectionInput" -import ColorPicker from "./ColorPicker" -import { Utils } from "../../Utils" -import Loc from "../../Models/Loc" -import BaseUIElement from "../BaseUIElement" -import LengthInput from "./LengthInput" -import { GeoOperations } from "../../Logic/GeoOperations" -import { Unit } from "../../Models/Unit" -import { FixedInputElement } from "./FixedInputElement" -import WikidataSearchBox from "../Wikipedia/WikidataSearchBox" -import Wikidata from "../../Logic/Web/Wikidata" -import Table from "../Base/Table" -import Combine from "../Base/Combine" -import Title from "../Base/Title" -import InputElementMap from "./InputElementMap" -import Translations from "../i18n/Translations" -import { Translation } from "../i18n/Translation" -import Locale from "../i18n/Locale" -import { RasterLayerPolygon } from "../../Models/RasterLayers" - -export class TextFieldDef { - public readonly name: string - /* - * An explanation for the theme builder. - * This can indicate which special input element is used, ... - * */ - public readonly explanation: string - protected inputmode?: string = undefined - - constructor(name: string, explanation: string | BaseUIElement) { - this.name = name - if (this.name.endsWith("textfield")) { - this.name = this.name.substr(0, this.name.length - "TextField".length) - } - if (this.name.endsWith("textfielddef")) { - this.name = this.name.substr(0, this.name.length - "TextFieldDef".length) - } - if (typeof explanation === "string") { - this.explanation = explanation - } else { - this.explanation = explanation.AsMarkdown() - } - } - - public getFeedback(s: string): Translation { - const tr = Translations.t.validation[this.name] - if (tr !== undefined) { - return tr["feedback"] - } - } - - public ConstructInputElement( - options: { - value?: UIEventSource - inputStyle?: string - feedback?: UIEventSource - placeholder?: string | Translation | UIEventSource - country?: () => string - location?: [number /*lat*/, number /*lon*/] - mapBackgroundLayer?: UIEventSource - unit?: Unit - args?: (string | number | boolean | any)[] // Extra arguments for the inputHelper, - feature?: any - } = {} - ): InputElement { - if (options.placeholder === undefined) { - options.placeholder = Translations.t.validation[this.name]?.description ?? this.name - } - - options["textArea"] = this.name === "text" - if (this.name === "text") { - options["htmlType"] = "area" - } - - const self = this - - if (options.unit !== undefined) { - // Reformatting is handled by the unit in this case - options["isValid"] = (str) => { - const denom = options.unit.findDenomination(str, options?.country) - if (denom === undefined) { - return false - } - const stripped = denom[0] - return self.isValid(stripped, options.country) - } - } else { - options["isValid"] = (str) => self.isValid(str, options.country) - } - options["cssText"] = "width: 100%;" - - options["inputMode"] = this.inputmode - if (this.inputmode === "text") { - options["htmlType"] = "area" - options["textAreaRows"] = 4 - } - - const textfield = new TextField(options) - let input: InputElement = textfield - if (options.feedback) { - textfield.GetRawValue().addCallback((v) => { - if (self.isValid(v, options.country)) { - options.feedback.setData(undefined) - } else { - options.feedback.setData(self.getFeedback(v)) - } - }) - } - - if (this.reformat && options.unit === undefined) { - input.GetValue().addCallbackAndRun((str) => { - if (!options["isValid"](str, options.country)) { - return - } - const formatted = this.reformat(str, options.country) - input.GetValue().setData(formatted) - }) - } - - if (options.unit) { - // We need to apply a unit. - // This implies: - // We have to create a dropdown with applicable denominations, and fuse those values - const unit = options.unit - - const isSingular = input.GetValue().map((str) => str?.trim() === "1") - - const unitDropDown = - unit.denominations.length === 1 - ? new FixedInputElement( - unit.denominations[0].getToggledHuman(isSingular), - unit.denominations[0] - ) - : new DropDown( - "", - unit.denominations.map((denom) => { - return { - shown: denom.getToggledHuman(isSingular), - value: denom, - } - }) - ) - unitDropDown.GetValue().setData(unit.getDefaultInput(options.country)) - unitDropDown.SetClass("w-min") - - const fixedDenom = unit.denominations.length === 1 ? unit.denominations[0] : undefined - input = new CombinedInputElement( - input, - unitDropDown, - // combine the value from the textfield and the dropdown into the resulting value that should go into OSM - (text, denom) => { - if (denom === undefined) { - return text - } - return denom?.canonicalValue(text, true) - }, - (valueWithDenom: string) => { - // Take the value from OSM and feed it into the textfield and the dropdown - const withDenom = unit.findDenomination(valueWithDenom, options?.country) - if (withDenom === undefined) { - // Not a valid value at all - we give it undefined and leave the details up to the other elements (but we keep the previous denomination) - return [undefined, fixedDenom] - } - const [strippedText, denom] = withDenom - if (strippedText === undefined) { - return [undefined, fixedDenom] - } - return [strippedText, denom] - } - ).SetClass("flex") - } - const helper = this.inputHelper(input.GetValue(), { - location: options.location, - mapBackgroundLayer: options.mapBackgroundLayer, - args: options.args, - feature: options.feature, - })?.SetClass("block") - if (helper !== undefined) { - input = new CombinedInputElement( - input, - helper, - (a, _) => a, // We can ignore b, as they are linked earlier - (a) => [a, a] - ).SetClass("block w-full") - } - if (this.postprocess !== undefined) { - input = new InputElementMap( - input, - (a, b) => a === b, - this.postprocess, - this.undoPostprocess - ) - } - - return input - } - - protected isValid(string: string, requestCountry: () => string): boolean { - return true - } - - protected reformat(s: string, country?: () => string): string { - return s - } - - /** - * Modification to make before the string is uploaded to OSM - */ - protected postprocess(s: string): string { - return s - } - - protected undoPostprocess(s: string): string { - return s - } - - protected inputHelper( - value: UIEventSource, - options?: { - location: [number, number] - mapBackgroundLayer?: UIEventSource - args: (string | number | boolean | any)[] - feature?: any - } - ): InputElement { - return undefined - } -} - -class WikidataTextField extends TextFieldDef { - constructor() { - super( - "wikidata", - new Combine([ - "A wikidata identifier, e.g. Q42.", - new Title("Helper arguments"), - new Table( - ["name", "doc"], - [ - ["key", "the value of this tag will initialize search (default: name)"], - [ - "options", - new Combine([ - "A JSON-object of type `{ removePrefixes: string[], removePostfixes: string[] }`.", - new Table( - ["subarg", "doc"], - [ - [ - "removePrefixes", - "remove these snippets of text from the start of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes", - ], - [ - "removePostfixes", - "remove these snippets of text from the end of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes.", - ], - [ - "instanceOf", - "A list of Q-identifier which indicates that the search results _must_ be an entity of this type, e.g. [`Q5`](https://www.wikidata.org/wiki/Q5) for humans", - ], - [ - "notInstanceof", - "A list of Q-identifiers which indicates that the search results _must not_ be an entity of this type, e.g. [`Q79007`](https://www.wikidata.org/wiki/Q79007) to filter away all streets from the search results", - ], - ] - ), - ]), - ], - ] - ), - new Title("Example usage"), - `The following is the 'freeform'-part of a layer config which will trigger a search for the wikidata item corresponding with the name of the selected feature. It will also remove '-street', '-square', ... if found at the end of the name - -\`\`\`json -"freeform": { - "key": "name:etymology:wikidata", - "type": "wikidata", - "helperArgs": [ - "name", - { - "removePostfixes": {"en": [ - "street", - "boulevard", - "path", - "square", - "plaza", - ], - "nl": ["straat","plein","pad","weg",laan"], - "fr":["route (de|de la|de l'| de le)"] - }, - - "#": "Remove streets and parks from the search results:" - "notInstanceOf": ["Q79007","Q22698"] - } - - ] -} -\`\`\` - -Another example is to search for species and trees: - -\`\`\`json - "freeform": { - "key": "species:wikidata", - "type": "wikidata", - "helperArgs": [ - "species", - { - "instanceOf": [10884, 16521] - }] - } -\`\`\` -`, - ]) - ) - } - - public isValid(str): boolean { - if (str === undefined) { - return false - } - if (str.length <= 2) { - return false - } - return !str.split(";").some((str) => Wikidata.ExtractKey(str) === undefined) - } - - public reformat(str) { - if (str === undefined) { - return undefined - } - let out = str - .split(";") - .map((str) => Wikidata.ExtractKey(str)) - .join("; ") - if (str.endsWith(";")) { - out = out + ";" - } - return out - } - - public inputHelper(currentValue, inputHelperOptions) { - const args = inputHelperOptions.args ?? [] - const searchKey = args[0] ?? "name" - - const searchFor = ( - (inputHelperOptions.feature?.properties[searchKey]?.toLowerCase() ?? "") - ) - - let searchForValue: UIEventSource = new UIEventSource(searchFor) - const options: any = args[1] - if (searchFor !== undefined && options !== undefined) { - const prefixes = >options["removePrefixes"] ?? [] - const postfixes = >options["removePostfixes"] ?? [] - const defaultValueCandidate = Locale.language.map((lg) => { - const prefixesUnrwapped: RegExp[] = ( - Array.isArray(prefixes) ? prefixes : prefixes[lg] ?? [] - ).map((s) => new RegExp("^" + s, "i")) - const postfixesUnwrapped: RegExp[] = ( - Array.isArray(postfixes) ? postfixes : postfixes[lg] ?? [] - ).map((s) => new RegExp(s + "$", "i")) - let clipped = searchFor - - for (const postfix of postfixesUnwrapped) { - const match = searchFor.match(postfix) - if (match !== null) { - clipped = searchFor.substring(0, searchFor.length - match[0].length) - break - } - } - - for (const prefix of prefixesUnrwapped) { - const match = searchFor.match(prefix) - if (match !== null) { - clipped = searchFor.substring(match[0].length) - break - } - } - return clipped - }) - - defaultValueCandidate.addCallbackAndRun((clipped) => searchForValue.setData(clipped)) - } - - let instanceOf: number[] = Utils.NoNull( - (options?.instanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)) - ) - let notInstanceOf: number[] = Utils.NoNull( - (options?.notInstanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)) - ) - - return new WikidataSearchBox({ - value: currentValue, - searchText: searchForValue, - instanceOf, - notInstanceOf, - }) - } -} - -class OpeningHoursTextField extends TextFieldDef { - constructor() { - super( - "opening_hours", - new Combine([ - "Has extra elements to easily input when a POI is opened.", - new Title("Helper arguments"), - new Table( - ["name", "doc"], - [ - [ - "options", - new Combine([ - "A JSON-object of type `{ prefix: string, postfix: string }`. ", - new Table( - ["subarg", "doc"], - [ - [ - "prefix", - "Piece of text that will always be added to the front of the generated opening hours. If the OSM-data does not start with this, it will fail to parse.", - ], - [ - "postfix", - "Piece of text that will always be added to the end of the generated opening hours", - ], - ] - ), - ]), - ], - ] - ), - new Title("Example usage"), - "To add a conditional (based on time) access restriction:\n\n```\n" + - ` -"freeform": { - "key": "access:conditional", - "type": "opening_hours", - "helperArgs": [ - { - "prefix":"no @ (", - "postfix":")" - } - ] -}` + - "\n```\n\n*Don't forget to pass the prefix and postfix in the rendering as well*: `{opening_hours_table(opening_hours,yes @ &LPARENS, &RPARENS )`", - ]) - ) - } - - isValid() { - return true - } - - reformat(str) { - return str - } - - inputHelper( - value: UIEventSource, - inputHelperOptions: { - location: [number, number] - mapBackgroundLayer?: UIEventSource - args: (string | number | boolean | any)[] - feature?: any - } - ) { - const args = (inputHelperOptions.args ?? [])[0] - const prefix = args?.prefix ?? "" - const postfix = args?.postfix ?? "" - - return new OpeningHoursInput(value, prefix, postfix) - } -} - -class UrlTextfieldDef extends TextFieldDef { - declare inputmode: "url" - - constructor() { - super( - "url", - "The validatedTextField will format URLs to always be valid and have a https://-header (even though the 'https'-part will be hidden from the user" - ) - } - - postprocess(str: string) { - if (str === undefined) { - return undefined - } - if (!str.startsWith("http://") || !str.startsWith("https://")) { - return "https://" + str - } - return str - } - - undoPostprocess(str: string) { - if (str === undefined) { - return undefined - } - if (str.startsWith("http://")) { - return str.substr("http://".length) - } - if (str.startsWith("https://")) { - return str.substr("https://".length) - } - return str - } - - reformat(str: string): string { - try { - let url: URL - // str = str.toLowerCase() // URLS are case sensitive. Lowercasing them might break some URLS. See #763 - if ( - !str.startsWith("http://") && - !str.startsWith("https://") && - !str.startsWith("http:") - ) { - url = new URL("https://" + str) - } else { - url = new URL(str) - } - const blacklistedTrackingParams = [ - "fbclid", // Oh god, how I hate the fbclid. Let it burn, burn in hell! - "gclid", - "cmpid", - "agid", - "utm", - "utm_source", - "utm_medium", - "campaignid", - "campaign", - "AdGroupId", - "AdGroup", - "TargetId", - "msclkid", - ] - for (const dontLike of blacklistedTrackingParams) { - url.searchParams.delete(dontLike.toLowerCase()) - } - let cleaned = url.toString() - if (cleaned.endsWith("/") && !str.endsWith("/")) { - // Do not add a trailing '/' if it wasn't typed originally - cleaned = cleaned.substr(0, cleaned.length - 1) - } - - if (cleaned.startsWith("https://")) { - cleaned = cleaned.substr("https://".length) - } - - return cleaned - } catch (e) { - console.error(e) - return undefined - } - } - - isValid(str: string): boolean { - try { - if ( - !str.startsWith("http://") && - !str.startsWith("https://") && - !str.startsWith("http:") - ) { - str = "https://" + str - } - const url = new URL(str) - const dotIndex = url.host.indexOf(".") - return dotIndex > 0 && url.host[url.host.length - 1] !== "." - } catch (e) { - return false - } - } -} - -class StringTextField extends TextFieldDef { - constructor() { - super("string", "A simple piece of text") - } -} - -class TextTextField extends TextFieldDef { - declare inputmode: "text" - constructor() { - super("text", "A longer piece of text. Uses an textArea instead of a textField") - } -} - -class DateTextField extends TextFieldDef { - constructor() { - super("date", "A date with date picker") - } - - isValid = (str) => { - return !isNaN(new Date(str).getTime()) - } - - reformat(str) { - const d = new Date(str) - let month = "" + (d.getMonth() + 1) - let day = "" + d.getDate() - const year = d.getFullYear() - - if (month.length < 2) month = "0" + month - if (day.length < 2) day = "0" + day - - return [year, month, day].join("-") - } - - inputHelper(value) { - return new SimpleDatePicker(value) - } -} - -class LengthTextField extends TextFieldDef { - inputMode: "decimal" - - 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"]' - ) - } - - isValid = (str) => { - const t = Number(str) - return !isNaN(t) - } - - inputHelper = ( - value: UIEventSource, - options: { - location?: [number, number] - args?: string[] - feature?: any - mapBackgroundLayer?: Store - } - ) => { - options = options ?? {} - options.location = options.location ?? [0, 0] - - const args = options.args ?? [] - let zoom = 19 - if (args[0]) { - zoom = Number(args[0]) - if (isNaN(zoom)) { - console.error( - "Invalid zoom level for argument at 'length'-input. The offending argument is: ", - args[0], - " (using 19 instead)" - ) - zoom = 19 - } - } - - // Bit of a hack: we project the centerpoint to the closes point on the road - if available - if (options?.feature !== undefined && options.feature.geometry.type !== "Point") { - const lonlat = <[number, number]>[...options.location] - lonlat.reverse(/*Changes a clone, this is safe */) - options.location = <[number, number]>( - GeoOperations.nearestPoint(options.feature, lonlat).geometry.coordinates - ) - options.location.reverse(/*Changes a clone, this is safe */) - } - - const location = new UIEventSource({ - lat: options.location[0], - lon: options.location[1], - zoom: zoom, - }) - if (args[1]) { - // The arguments indicate the preferred background type - options.mapBackgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo( - location, - new ImmutableStore(args[1].split(",")) - ) - } - const background = options?.mapBackgroundLayer - const li = new LengthInput( - new UIEventSource(background.data), - location, - value - ) - li.SetStyle("height: 20rem;") - return li - } -} - -class FloatTextField extends TextFieldDef { - inputmode = "decimal" - - constructor(name?: string, explanation?: string) { - super(name ?? "float", explanation ?? "A decimal") - } - - isValid(str) { - return !isNaN(Number(str)) && !str.endsWith(".") && !str.endsWith(",") - } - - reformat(str): string { - return "" + Number(str) - } - - getFeedback(s: string): Translation { - if (isNaN(Number(s))) { - return Translations.t.validation.nat.notANumber - } - - return undefined - } -} - -class IntTextField extends FloatTextField { - inputMode = "numeric" - - constructor(name?: string, explanation?: string) { - super(name ?? "int", explanation ?? "A number") - } - - isValid(str): boolean { - str = "" + str - return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) - } - - getFeedback(s: string): Translation { - const n = Number(s) - if (isNaN(n)) { - return Translations.t.validation.nat.notANumber - } - if (Math.floor(n) !== n) { - return Translations.t.validation.nat.mustBeWhole - } - return undefined - } -} - -class NatTextField extends IntTextField { - inputMode = "numeric" - - constructor(name?: string, explanation?: string) { - super(name ?? "nat", explanation ?? "A positive number or zero") - } - - isValid(str): boolean { - if (str === undefined) { - return false - } - str = "" + str - - return str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) >= 0 - } - - getFeedback(s: string): Translation { - const spr = super.getFeedback(s) - if (spr !== undefined) { - return spr - } - const n = Number(s) - if (n < 0) { - return Translations.t.validation.nat.mustBePositive - } - return undefined - } -} - -class PNatTextField extends NatTextField { - inputmode = "numeric" - - constructor() { - super("pnat", "A strict positive number") - } - - getFeedback(s: string): Translation { - const spr = super.getFeedback(s) - if (spr !== undefined) { - return spr - } - if (Number(s) === 0) { - return Translations.t.validation.pnat.noZero - } - return undefined - } - - isValid = (str) => { - if (!super.isValid(str)) { - return false - } - return Number(str) > 0 - } -} - -class PFloatTextField extends FloatTextField { - inputmode = "decimal" - - constructor() { - super("pfloat", "A positive decimal (inclusive zero)") - } - - isValid = (str) => - !isNaN(Number(str)) && Number(str) >= 0 && !str.endsWith(".") && !str.endsWith(",") - - getFeedback(s: string): Translation { - const spr = super.getFeedback(s) - if (spr !== undefined) { - return spr - } - if (Number(s) < 0) { - return Translations.t.validation.nat.mustBePositive - } - return undefined - } -} - -class EmailTextField extends TextFieldDef { - inputmode = "email" - - constructor() { - super("email", "An email adress") - } - - isValid = (str) => { - if (str === undefined) { - return false - } - str = str.trim() - if (str.startsWith("mailto:")) { - str = str.substring("mailto:".length) - } - return EmailValidator.validate(str) - } - - reformat = (str) => { - if (str === undefined) { - return undefined - } - str = str.trim() - if (str.startsWith("mailto:")) { - str = str.substring("mailto:".length) - } - return str - } - - getFeedback(s: string): Translation { - if (s.indexOf("@") < 0) { - return Translations.t.validation.email.noAt - } - - return super.getFeedback(s) - } -} - -class PhoneTextField extends TextFieldDef { - inputmode = "tel" - - constructor() { - super("phone", "A phone number") - } - - isValid(str, country: () => string): boolean { - if (str === undefined) { - return false - } - if (str.startsWith("tel:")) { - str = str.substring("tel:".length) - } - let countryCode = undefined - if (country !== undefined) { - countryCode = country()?.toUpperCase() - } - return parsePhoneNumberFromString(str, countryCode)?.isValid() ?? false - } - - reformat = (str, country: () => string) => { - if (str.startsWith("tel:")) { - str = str.substring("tel:".length) - } - return parsePhoneNumberFromString( - str, - country()?.toUpperCase() as any - )?.formatInternational() - } -} - -class ColorTextField extends TextFieldDef { - constructor() { - super("color", "Shows a color picker") - } - - inputHelper = (value) => { - return new ColorPicker( - value.map( - (color) => { - return Utils.ColourNameToHex(color ?? "") - }, - [], - (str) => Utils.HexToColourName(str) - ) - ) - } -} - -class DirectionTextField extends IntTextField { - inputMode = "numeric" - - constructor() { - super( - "direction", - "A geographical direction, in degrees. 0° is north, 90° is east, ... Will return a value between 0 (incl) and 360 (excl)" - ) - } - - reformat(str): string { - const n = Number(str) % 360 - return "" + n - } - - inputHelper = (value, options) => { - const args = options.args ?? [] - options.location = options.location ?? [0, 0] - let zoom = 19 - if (args[0]) { - zoom = Number(args[0]) - if (isNaN(zoom)) { - throw "Invalid zoom level for argument at 'length'-input" - } - } - const location = new UIEventSource({ - lat: options.location[0], - lon: options.location[1], - zoom: zoom, - }) - if (args[1]) { - // We have a prefered map! - options.mapBackgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo( - location, - new UIEventSource(args[1].split(",")) - ) - } - const di = new DirectionInput(options.mapBackgroundLayer, location, value) - di.SetStyle("max-width: 25rem;") - - return di - } -} - -export default class ValidatedTextField { - private static AllTextfieldDefs: TextFieldDef[] = [ - new StringTextField(), - new TextTextField(), - new DateTextField(), - new NatTextField(), - new IntTextField(), - new LengthTextField(), - new DirectionTextField(), - new WikidataTextField(), - new PNatTextField(), - new FloatTextField(), - new PFloatTextField(), - new EmailTextField(), - new UrlTextfieldDef(), - new PhoneTextField(), - new OpeningHoursTextField(), - new ColorTextField(), - ] - public static allTypes: Map = ValidatedTextField.allTypesDict() - public static ForType(type: string = "string"): TextFieldDef { - const def = ValidatedTextField.allTypes.get(type) - if (def === undefined) { - console.warn( - "Something tried to load a validated text field named", - type, - "but this type does not exist" - ) - return this.ForType() - } - return def - } - - public static HelpText(): BaseUIElement { - const explanations: BaseUIElement[] = ValidatedTextField.AllTextfieldDefs.map((type) => - new Combine([new Title(type.name, 3), type.explanation]).SetClass("flex flex-col") - ) - return new Combine([ - new Title("Available types for text fields", 1), - "The listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them", - ...explanations, - ]).SetClass("flex flex-col") - } - - public static AvailableTypes(): string[] { - return ValidatedTextField.AllTextfieldDefs.map((tp) => tp.name) - } - - private static allTypesDict(): Map { - const types = new Map() - for (const tp of ValidatedTextField.AllTextfieldDefs) { - types[tp.name] = tp - types.set(tp.name, tp) - } - return types - } -} diff --git a/UI/InputElement/Helpers/DirectionInput.svelte b/UI/InputElement/Helpers/DirectionInput.svelte new file mode 100644 index 0000000000..cf63be378c --- /dev/null +++ b/UI/InputElement/Helpers/DirectionInput.svelte @@ -0,0 +1,70 @@ + + +
onPosChange(e.x, e.y)} + on:mousedown={e => { + isDown = true + onPosChange(e.clientX, e.clientY) + } } + on:mousemove={e => { + if(isDown){ + onPosChange(e.clientX, e.clientY) + + }}} + + on:mouseup={() => { + isDown = false + } } + on:touchmove={e => onPosChange(e.touches[0].clientX, e.touches[0].clientY)} + + + on:touchstart={e => onPosChange(e.touches[0].clientX, e.touches[0].clientY)}> +
+ +
+ +
+ + + + +
+
diff --git a/UI/InputElement/Helpers/LocationInput.svelte b/UI/InputElement/Helpers/LocationInput.svelte new file mode 100644 index 0000000000..c6767293b0 --- /dev/null +++ b/UI/InputElement/Helpers/LocationInput.svelte @@ -0,0 +1,42 @@ + + +
+
+ +
+ +
+ Svg.move_arrows_svg().SetClass("h-full")}> +
+ + + +
diff --git a/UI/InputElement/InputHelper.svelte b/UI/InputElement/InputHelper.svelte new file mode 100644 index 0000000000..e64305a67c --- /dev/null +++ b/UI/InputElement/InputHelper.svelte @@ -0,0 +1,13 @@ + diff --git a/UI/InputElement/InputHelpers.ts b/UI/InputElement/InputHelpers.ts new file mode 100644 index 0000000000..ff6c14f8bb --- /dev/null +++ b/UI/InputElement/InputHelpers.ts @@ -0,0 +1,16 @@ +import { AvailableRasterLayers } from "../../Models/RasterLayers" + +export type AvailableInputHelperType = typeof InputHelpers.AvailableInputHelpers[number] + +export default class InputHelpers { + public static readonly AvailableInputHelpers = [] as const + /** + * To port + * direction + * opening_hours + * color + * length + * date + * wikidata + */ +} diff --git a/UI/InputElement/ValidatedTextField.ts b/UI/InputElement/ValidatedTextField.ts new file mode 100644 index 0000000000..3ed4946e00 --- /dev/null +++ b/UI/InputElement/ValidatedTextField.ts @@ -0,0 +1,119 @@ +import BaseUIElement from "../BaseUIElement" +import Combine from "../Base/Combine" +import Title from "../Base/Title" +import Translations from "../i18n/Translations" +import { Translation } from "../i18n/Translation" +import WikidataValidator from "./Validators/WikidataValidator" +import StringValidator from "./Validators/StringValidator" +import TextValidator from "./Validators/TextValidator" +import DateValidator from "./Validators/DateValidator" +import LengthValidator from "./Validators/LengthValidator" +import IntValidator from "./Validators/IntValidator" +import EmailValidator from "./Validators/EmailValidator" +import DirectionValidator from "./Validators/DirectionValidator" +import NatValidator from "./Validators/NatValidator" +import OpeningHoursValidator from "./Validators/OpeningHoursValidator" +import PFloatValidator from "./Validators/PFloatValidator" +import ColorValidator from "./Validators/ColorValidator" +import PhoneValidator from "./Validators/PhoneValidator" +import UrlValidator from "./Validators/UrlValidator" +import FloatValidator from "./Validators/FloatValidator" +import PNatValidator from "./Validators/PNatValidator" + +/** + * A 'TextFieldValidator' contains various methods to check and cleanup an entered value or to give feedback. + * They also double as an index of supported types for textfields in MapComplete + */ +export abstract class Validator { + public readonly name: string + /* + * An explanation for the theme builder. + * This can indicate which special input element is used, ... + * */ + public readonly explanation: string + /** + * What HTML-inputmode to use + */ + public readonly inputmode?: string + + constructor(name: string, explanation: string | BaseUIElement, inputmode?: string) { + this.name = name + this.inputmode = inputmode + if (this.name.endsWith("textfield")) { + this.name = this.name.substr(0, this.name.length - "TextField".length) + } + if (this.name.endsWith("textfielddef")) { + this.name = this.name.substr(0, this.name.length - "TextFieldDef".length) + } + if (typeof explanation === "string") { + this.explanation = explanation + } else { + this.explanation = explanation.AsMarkdown() + } + } + + /** + * Gets a piece of feedback. By default, validation. will be used, resulting in a generic 'not a valid '. + * However, inheritors might overwrite this to give more specific feedback + * @param s + */ + public getFeedback(s: string): Translation { + const tr = Translations.t.validation[this.name] + if (tr !== undefined) { + return tr["feedback"] + } + } + + public isValid(string: string, requestCountry: () => string): boolean { + return true + } + + public reformat(s: string, country?: () => string): string { + return s + } +} + +export default class Validators { + private static readonly AllValidators: ReadonlyArray = [ + new StringValidator(), + new TextValidator(), + new DateValidator(), + new NatValidator(), + new IntValidator(), + new LengthValidator(), + new DirectionValidator(), + new WikidataValidator(), + new PNatValidator(), + new FloatValidator(), + new PFloatValidator(), + new EmailValidator(), + new UrlValidator(), + new PhoneValidator(), + new OpeningHoursValidator(), + new ColorValidator(), + ] + public static allTypes: Map = Validators.allTypesDict() + + public static HelpText(): BaseUIElement { + const explanations: BaseUIElement[] = Validators.AllValidators.map((type) => + new Combine([new Title(type.name, 3), type.explanation]).SetClass("flex flex-col") + ) + return new Combine([ + new Title("Available types for text fields", 1), + "The listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them", + ...explanations, + ]).SetClass("flex flex-col") + } + + public static AvailableTypes(): string[] { + return Validators.AllValidators.map((tp) => tp.name) + } + + private static allTypesDict(): Map { + const types = new Map() + for (const tp of Validators.AllValidators) { + types.set(tp.name, tp) + } + return types + } +} diff --git a/UI/InputElement/Validators/ColorValidator.ts b/UI/InputElement/Validators/ColorValidator.ts new file mode 100644 index 0000000000..2022edecbd --- /dev/null +++ b/UI/InputElement/Validators/ColorValidator.ts @@ -0,0 +1,7 @@ +import { Validator } from "../ValidatedTextField" + +export default class ColorValidator extends Validator { + constructor() { + super("color", "Shows a color picker") + } +} diff --git a/UI/InputElement/Validators/DateValidator.ts b/UI/InputElement/Validators/DateValidator.ts new file mode 100644 index 0000000000..1ebbd67bc1 --- /dev/null +++ b/UI/InputElement/Validators/DateValidator.ts @@ -0,0 +1,23 @@ +import { Validator } from "../ValidatedTextField" + +export default class DateValidator extends Validator { + constructor() { + super("date", "A date with date picker") + } + + isValid(str: string): boolean { + return !isNaN(new Date(str).getTime()) + } + + reformat(str: string) { + const d = new Date(str) + let month = "" + (d.getMonth() + 1) + let day = "" + d.getDate() + const year = d.getFullYear() + + if (month.length < 2) month = "0" + month + if (day.length < 2) day = "0" + day + + return [year, month, day].join("-") + } +} diff --git a/UI/InputElement/Validators/DirectionValidator.ts b/UI/InputElement/Validators/DirectionValidator.ts new file mode 100644 index 0000000000..582edf58a2 --- /dev/null +++ b/UI/InputElement/Validators/DirectionValidator.ts @@ -0,0 +1,17 @@ +import { Validator } from "../ValidatedTextField" +import IntValidator from "./IntValidator"; + +export default class DirectionValidator extends IntValidator { + constructor() { + super( + "direction", + "A geographical direction, in degrees. 0° is north, 90° is east, ... Will return a value between 0 (incl) and 360 (excl)" + ) + } + + reformat(str): string { + const n = Number(str) % 360 + return "" + n + } + +} diff --git a/UI/InputElement/Validators/EmailValidator.ts b/UI/InputElement/Validators/EmailValidator.ts new file mode 100644 index 0000000000..8702bdff89 --- /dev/null +++ b/UI/InputElement/Validators/EmailValidator.ts @@ -0,0 +1,39 @@ +import { Validator } from "../ValidatedTextField.js" +import { Translation } from "../../i18n/Translation.js" +import Translations from "../../i18n/Translations.js" +import * as emailValidatorLibrary from "email-validator" +export default class EmailValidator extends Validator { + constructor() { + super("email", "An email adress", "email") + } + + isValid = (str) => { + if (str === undefined) { + return false + } + str = str.trim() + if (str.startsWith("mailto:")) { + str = str.substring("mailto:".length) + } + return emailValidatorLibrary.validate(str) + } + + reformat = (str) => { + if (str === undefined) { + return undefined + } + str = str.trim() + if (str.startsWith("mailto:")) { + str = str.substring("mailto:".length) + } + return str + } + + getFeedback(s: string): Translation { + if (s.indexOf("@") < 0) { + return Translations.t.validation.email.noAt + } + + return super.getFeedback(s) + } +} diff --git a/UI/InputElement/Validators/FloatValidator.ts b/UI/InputElement/Validators/FloatValidator.ts new file mode 100644 index 0000000000..1bcf554045 --- /dev/null +++ b/UI/InputElement/Validators/FloatValidator.ts @@ -0,0 +1,27 @@ +import { Translation } from "../../i18n/Translation" +import Translations from "../../i18n/Translations" +import { Validator } from "../ValidatedTextField" + +export default class FloatValidator extends Validator { + inputmode = "decimal" + + constructor(name?: string, explanation?: string) { + super(name ?? "float", explanation ?? "A decimal number", "decimal") + } + + isValid(str) { + return !isNaN(Number(str)) && !str.endsWith(".") && !str.endsWith(",") + } + + reformat(str): string { + return "" + Number(str) + } + + getFeedback(s: string): Translation { + if (isNaN(Number(s))) { + return Translations.t.validation.nat.notANumber + } + + return undefined + } +} diff --git a/UI/InputElement/Validators/IntValidator.ts b/UI/InputElement/Validators/IntValidator.ts new file mode 100644 index 0000000000..0b349cbe82 --- /dev/null +++ b/UI/InputElement/Validators/IntValidator.ts @@ -0,0 +1,29 @@ +import { Translation } from "../../i18n/Translation" +import Translations from "../../i18n/Translations" +import { Validator } from "../ValidatedTextField" + +export default class IntValidator extends Validator { + constructor(name?: string, explanation?: string) { + super( + name ?? "int", + explanation ?? "A whole number, either positive, negative or zero", + "numeric" + ) + } + + isValid(str): boolean { + str = "" + str + return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) + } + + getFeedback(s: string): Translation { + const n = Number(s) + if (isNaN(n)) { + return Translations.t.validation.nat.notANumber + } + if (Math.floor(n) !== n) { + return Translations.t.validation.nat.mustBeWhole + } + return undefined + } +} diff --git a/UI/InputElement/Validators/LengthValidator.ts b/UI/InputElement/Validators/LengthValidator.ts new file mode 100644 index 0000000000..c9e879953b --- /dev/null +++ b/UI/InputElement/Validators/LengthValidator.ts @@ -0,0 +1,16 @@ +import { Validator } from "../ValidatedTextField" + +export default class LengthValidator extends Validator { + 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"]', + "decimal" + ) + } + + isValid = (str) => { + const t = Number(str) + return !isNaN(t) + } +} diff --git a/UI/InputElement/Validators/NatValidator.ts b/UI/InputElement/Validators/NatValidator.ts new file mode 100644 index 0000000000..fbc77758c1 --- /dev/null +++ b/UI/InputElement/Validators/NatValidator.ts @@ -0,0 +1,30 @@ +import IntValidator from "./IntValidator" +import { Translation } from "../../i18n/Translation" +import Translations from "../../i18n/Translations" + +export default class NatValidator extends IntValidator { + constructor(name?: string, explanation?: string) { + super(name ?? "nat", explanation ?? "A whole, positive number or zero") + } + + isValid(str): boolean { + if (str === undefined) { + return false + } + str = "" + str + + return str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) >= 0 + } + + getFeedback(s: string): Translation { + const spr = super.getFeedback(s) + if (spr !== undefined) { + return spr + } + const n = Number(s) + if (n < 0) { + return Translations.t.validation.nat.mustBePositive + } + return undefined + } +} diff --git a/UI/InputElement/Validators/OpeningHoursValidator.ts b/UI/InputElement/Validators/OpeningHoursValidator.ts new file mode 100644 index 0000000000..85760df64a --- /dev/null +++ b/UI/InputElement/Validators/OpeningHoursValidator.ts @@ -0,0 +1,54 @@ +import { Validator } from "../ValidatedTextField" +import Combine from "../../Base/Combine" +import Title from "../../Base/Title" +import Table from "../../Base/Table" + +export default class OpeningHoursValidator extends Validator { + constructor() { + super( + "opening_hours", + new Combine([ + "Has extra elements to easily input when a POI is opened.", + new Title("Helper arguments"), + new Table( + ["name", "doc"], + [ + [ + "options", + new Combine([ + "A JSON-object of type `{ prefix: string, postfix: string }`. ", + new Table( + ["subarg", "doc"], + [ + [ + "prefix", + "Piece of text that will always be added to the front of the generated opening hours. If the OSM-data does not start with this, it will fail to parse.", + ], + [ + "postfix", + "Piece of text that will always be added to the end of the generated opening hours", + ], + ] + ), + ]), + ], + ] + ), + new Title("Example usage"), + "To add a conditional (based on time) access restriction:\n\n```\n" + + ` +"freeform": { + "key": "access:conditional", + "type": "opening_hours", + "helperArgs": [ + { + "prefix":"no @ (", + "postfix":")" + } + ] +}` + + "\n```\n\n*Don't forget to pass the prefix and postfix in the rendering as well*: `{opening_hours_table(opening_hours,yes @ &LPARENS, &RPARENS )`", + ]) + ) + } +} diff --git a/UI/InputElement/Validators/PFloatValidator.ts b/UI/InputElement/Validators/PFloatValidator.ts new file mode 100644 index 0000000000..9501631a89 --- /dev/null +++ b/UI/InputElement/Validators/PFloatValidator.ts @@ -0,0 +1,23 @@ +import { Translation } from "../../i18n/Translation" +import Translations from "../../i18n/Translations" +import { Validator } from "../ValidatedTextField" + +export default class PFloatValidator extends Validator { + constructor() { + super("pfloat", "A positive decimal number or zero") + } + + isValid = (str) => + !isNaN(Number(str)) && Number(str) >= 0 && !str.endsWith(".") && !str.endsWith(",") + + getFeedback(s: string): Translation { + const spr = super.getFeedback(s) + if (spr !== undefined) { + return spr + } + if (Number(s) < 0) { + return Translations.t.validation.nat.mustBePositive + } + return undefined + } +} diff --git a/UI/InputElement/Validators/PNatValidator.ts b/UI/InputElement/Validators/PNatValidator.ts new file mode 100644 index 0000000000..e1b024f638 --- /dev/null +++ b/UI/InputElement/Validators/PNatValidator.ts @@ -0,0 +1,27 @@ +import { Translation } from "../../i18n/Translation" +import Translations from "../../i18n/Translations" +import NatValidator from "./NatValidator" + +export default class PNatValidator extends NatValidator { + constructor() { + super("pnat", "A strict positive number") + } + + getFeedback(s: string): Translation { + const spr = super.getFeedback(s) + if (spr !== undefined) { + return spr + } + if (Number(s) === 0) { + return Translations.t.validation.pnat.noZero + } + return undefined + } + + isValid = (str) => { + if (!super.isValid(str)) { + return false + } + return Number(str) > 0 + } +} diff --git a/UI/InputElement/Validators/PhoneValidator.ts b/UI/InputElement/Validators/PhoneValidator.ts new file mode 100644 index 0000000000..db4fa3d26e --- /dev/null +++ b/UI/InputElement/Validators/PhoneValidator.ts @@ -0,0 +1,32 @@ +import { Validator } from "../ValidatedTextField" +import { parsePhoneNumberFromString } from "libphonenumber-js" + +export default class PhoneValidator extends Validator { + constructor() { + super("phone", "A phone number", "tel") + } + + isValid(str, country: () => string): boolean { + if (str === undefined) { + return false + } + if (str.startsWith("tel:")) { + str = str.substring("tel:".length) + } + let countryCode = undefined + if (country !== undefined) { + countryCode = country()?.toUpperCase() + } + return parsePhoneNumberFromString(str, countryCode)?.isValid() ?? false + } + + reformat = (str, country: () => string) => { + if (str.startsWith("tel:")) { + str = str.substring("tel:".length) + } + return parsePhoneNumberFromString( + str, + country()?.toUpperCase() as any + )?.formatInternational() + } +} diff --git a/UI/InputElement/Validators/StringValidator.ts b/UI/InputElement/Validators/StringValidator.ts new file mode 100644 index 0000000000..b058086804 --- /dev/null +++ b/UI/InputElement/Validators/StringValidator.ts @@ -0,0 +1,8 @@ +import { Validator } from "../ValidatedTextField" + +export default class StringValidator extends Validator { + constructor() { + super("string", "A simple piece of text") + } + +} diff --git a/UI/InputElement/Validators/TextValidator.ts b/UI/InputElement/Validators/TextValidator.ts new file mode 100644 index 0000000000..cad3b997a7 --- /dev/null +++ b/UI/InputElement/Validators/TextValidator.ts @@ -0,0 +1,7 @@ +import { Validator } from "../ValidatedTextField" + +export default class TextValidator extends Validator { + constructor() { + super("text", "A longer piece of text. Uses an textArea instead of a textField", "text") + } +} diff --git a/UI/InputElement/Validators/UrlValidator.ts b/UI/InputElement/Validators/UrlValidator.ts new file mode 100644 index 0000000000..16e9172f70 --- /dev/null +++ b/UI/InputElement/Validators/UrlValidator.ts @@ -0,0 +1,75 @@ +import { Validator } from "../ValidatedTextField" + +export default class UrlValidator extends Validator { + constructor() { + super( + "url", + "The validatedTextField will format URLs to always be valid and have a https://-header (even though the 'https'-part will be hidden from the user. Furthermore, some tracking parameters will be removed", + "url" + ) + } + reformat(str: string): string { + try { + let url: URL + // str = str.toLowerCase() // URLS are case sensitive. Lowercasing them might break some URLS. See #763 + if ( + !str.startsWith("http://") && + !str.startsWith("https://") && + !str.startsWith("http:") + ) { + url = new URL("https://" + str) + } else { + url = new URL(str) + } + const blacklistedTrackingParams = [ + "fbclid", // Oh god, how I hate the fbclid. Let it burn, burn in hell! + "gclid", + "cmpid", + "agid", + "utm", + "utm_source", + "utm_medium", + "campaignid", + "campaign", + "AdGroupId", + "AdGroup", + "TargetId", + "msclkid", + ] + for (const dontLike of blacklistedTrackingParams) { + url.searchParams.delete(dontLike.toLowerCase()) + } + let cleaned = url.toString() + if (cleaned.endsWith("/") && !str.endsWith("/")) { + // Do not add a trailing '/' if it wasn't typed originally + cleaned = cleaned.substr(0, cleaned.length - 1) + } + + if (!str.startsWith("http") && cleaned.startsWith("https://")) { + cleaned = cleaned.substr("https://".length) + } + + return cleaned + } catch (e) { + console.error(e) + return undefined + } + } + + isValid(str: string): boolean { + try { + if ( + !str.startsWith("http://") && + !str.startsWith("https://") && + !str.startsWith("http:") + ) { + str = "https://" + str + } + const url = new URL(str) + const dotIndex = url.host.indexOf(".") + return dotIndex > 0 && url.host[url.host.length - 1] !== "." + } catch (e) { + return false + } + } +} diff --git a/UI/InputElement/Validators/WikidataValidator.ts b/UI/InputElement/Validators/WikidataValidator.ts new file mode 100644 index 0000000000..fa1588e378 --- /dev/null +++ b/UI/InputElement/Validators/WikidataValidator.ts @@ -0,0 +1,179 @@ +import Combine from "../../Base/Combine" +import Title from "../../Base/Title" +import Table from "../../Base/Table" +import Wikidata from "../../../Logic/Web/Wikidata" +import { UIEventSource } from "../../../Logic/UIEventSource" +import Locale from "../../i18n/Locale" +import { Utils } from "../../../Utils" +import WikidataSearchBox from "../../Wikipedia/WikidataSearchBox" +import { Validator } from "../ValidatedTextField" + +export default class WikidataValidator extends Validator { + constructor() { + super( + "wikidata", + new Combine([ + "A wikidata identifier, e.g. Q42.", + new Title("Helper arguments"), + new Table( + ["name", "doc"], + [ + ["key", "the value of this tag will initialize search (default: name)"], + [ + "options", + new Combine([ + "A JSON-object of type `{ removePrefixes: string[], removePostfixes: string[] }`.", + new Table( + ["subarg", "doc"], + [ + [ + "removePrefixes", + "remove these snippets of text from the start of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes", + ], + [ + "removePostfixes", + "remove these snippets of text from the end of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes.", + ], + [ + "instanceOf", + "A list of Q-identifier which indicates that the search results _must_ be an entity of this type, e.g. [`Q5`](https://www.wikidata.org/wiki/Q5) for humans", + ], + [ + "notInstanceof", + "A list of Q-identifiers which indicates that the search results _must not_ be an entity of this type, e.g. [`Q79007`](https://www.wikidata.org/wiki/Q79007) to filter away all streets from the search results", + ], + ] + ), + ]), + ], + ] + ), + new Title("Example usage"), + `The following is the 'freeform'-part of a layer config which will trigger a search for the wikidata item corresponding with the name of the selected feature. It will also remove '-street', '-square', ... if found at the end of the name + +\`\`\`json +"freeform": { + "key": "name:etymology:wikidata", + "type": "wikidata", + "helperArgs": [ + "name", + { + "removePostfixes": {"en": [ + "street", + "boulevard", + "path", + "square", + "plaza", + ], + "nl": ["straat","plein","pad","weg",laan"], + "fr":["route (de|de la|de l'| de le)"] + }, + + "#": "Remove streets and parks from the search results:" + "notInstanceOf": ["Q79007","Q22698"] + } + + ] +} +\`\`\` + +Another example is to search for species and trees: + +\`\`\`json + "freeform": { + "key": "species:wikidata", + "type": "wikidata", + "helperArgs": [ + "species", + { + "instanceOf": [10884, 16521] + }] + } +\`\`\` +`, + ]) + ) + } + + public isValid(str): boolean { + if (str === undefined) { + return false + } + if (str.length <= 2) { + return false + } + return !str.split(";").some((str) => Wikidata.ExtractKey(str) === undefined) + } + + public reformat(str) { + if (str === undefined) { + return undefined + } + let out = str + .split(";") + .map((str) => Wikidata.ExtractKey(str)) + .join("; ") + if (str.endsWith(";")) { + out = out + ";" + } + return out + } + + public inputHelper(currentValue, inputHelperOptions) { + const args = inputHelperOptions.args ?? [] + const searchKey = args[0] ?? "name" + + const searchFor = ( + (inputHelperOptions.feature?.properties[searchKey]?.toLowerCase() ?? "") + ) + + let searchForValue: UIEventSource = new UIEventSource(searchFor) + const options: any = args[1] + if (searchFor !== undefined && options !== undefined) { + const prefixes = >options["removePrefixes"] ?? [] + const postfixes = >options["removePostfixes"] ?? [] + const defaultValueCandidate = Locale.language.map((lg) => { + const prefixesUnrwapped: RegExp[] = ( + Array.isArray(prefixes) ? prefixes : prefixes[lg] ?? [] + ).map((s) => new RegExp("^" + s, "i")) + const postfixesUnwrapped: RegExp[] = ( + Array.isArray(postfixes) ? postfixes : postfixes[lg] ?? [] + ).map((s) => new RegExp(s + "$", "i")) + let clipped = searchFor + + for (const postfix of postfixesUnwrapped) { + const match = searchFor.match(postfix) + if (match !== null) { + clipped = searchFor.substring(0, searchFor.length - match[0].length) + break + } + } + + for (const prefix of prefixesUnrwapped) { + const match = searchFor.match(prefix) + if (match !== null) { + clipped = searchFor.substring(match[0].length) + break + } + } + return clipped + }) + + defaultValueCandidate.addCallbackAndRun((clipped) => searchForValue.setData(clipped)) + } + + let instanceOf: number[] = Utils.NoNull( + (options?.instanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)) + ) + let notInstanceOf: number[] = Utils.NoNull( + (options?.notInstanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)) + ) + + return new WikidataSearchBox({ + value: currentValue, + searchText: searchForValue, + instanceOf, + notInstanceOf, + }) + } +} diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index f3232168b1..f6c5c95c6b 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -43,7 +43,7 @@ export class MapLibreAdaptor implements MapProperties { */ private _currentRasterLayer: string - constructor(maplibreMap: Store, state?: Partial>) { + constructor(maplibreMap: Store, state?: Partial) { this._maplibreMap = maplibreMap this.location = state?.location ?? new UIEventSource({ lon: 0, lat: 0 }) diff --git a/UI/Map/MaplibreMap.svelte b/UI/Map/MaplibreMap.svelte index dcd1a1cd67..7ebaa56b2b 100644 --- a/UI/Map/MaplibreMap.svelte +++ b/UI/Map/MaplibreMap.svelte @@ -16,6 +16,7 @@ */ export let map: Writable + export let attribution = true let center = {}; onMount(() => { @@ -28,6 +29,9 @@
diff --git a/UI/Map/ShowDataLayer.ts b/UI/Map/ShowDataLayer.ts index 3032a4eff3..6724680c2b 100644 --- a/UI/Map/ShowDataLayer.ts +++ b/UI/Map/ShowDataLayer.ts @@ -106,7 +106,7 @@ class PointRenderingLayer { store = new ImmutableStore(feature.properties) } const { html, iconAnchor } = this._config.RenderIcon(store, true) - html.SetClass("marker") + html.SetClass("marker cursor-pointer") const el = html.ConstructElement() if (this._onClick) { @@ -244,7 +244,7 @@ class LineRenderingLayer { }, }) - this._visibility.addCallbackAndRunD((visible) => { + this._visibility?.addCallbackAndRunD((visible) => { map.setLayoutProperty(linelayer, "visibility", visible ? "visible" : "none") map.setLayoutProperty(polylayer, "visibility", visible ? "visible" : "none") }) diff --git a/UI/Map/ShowDataLayerOptions.ts b/UI/Map/ShowDataLayerOptions.ts index c20960a72e..b81175d906 100644 --- a/UI/Map/ShowDataLayerOptions.ts +++ b/UI/Map/ShowDataLayerOptions.ts @@ -1,6 +1,5 @@ import FeatureSource from "../../Logic/FeatureSource/FeatureSource" import { Store, UIEventSource } from "../../Logic/UIEventSource" -import { OsmTags } from "../../Models/OsmFeature" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { Feature } from "geojson" diff --git a/UI/Popup/HistogramViz.ts b/UI/Popup/HistogramViz.ts index d83d1dcf69..fffc451d40 100644 --- a/UI/Popup/HistogramViz.ts +++ b/UI/Popup/HistogramViz.ts @@ -1,12 +1,13 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import Histogram from "../BigComponents/Histogram" +import { Feature } from "geojson" export class HistogramViz implements SpecialVisualization { funcName = "histogram" docs = "Create a histogram for a list of given values, read from the properties." example = - "`{histogram('some_key')}` with properties being `{some_key: ['a','b','a','c']} to create a histogram" + '`{histogram(\'some_key\')}` with properties being `{some_key: ["a","b","a","c"]} to create a histogram' args = [ { name: "key", @@ -29,6 +30,22 @@ export class HistogramViz implements SpecialVisualization { }, ] + structuredExamples(): { feature: Feature; args: string[] }[] { + return [ + { + feature: { + type: "Feature", + properties: { values: `["a","b","a","b","b","b","c","c","c","d","d"]` }, + geometry: { + type: "Point", + coordinates: [0, 0], + }, + }, + args: ["values"], + }, + ] + } + constr( state: SpecialVisualizationState, tagSource: UIEventSource>, diff --git a/UI/Popup/MinimapViz.ts b/UI/Popup/MinimapViz.ts index ccf061f68f..6daa6e97a1 100644 --- a/UI/Popup/MinimapViz.ts +++ b/UI/Popup/MinimapViz.ts @@ -5,10 +5,7 @@ import { Feature } from "geojson" import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" import SvelteUIElement from "../Base/SvelteUIElement" import MaplibreMap from "../Map/MaplibreMap.svelte" -import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter" -import FilteredLayer from "../../Models/FilteredLayer" import ShowDataLayer from "../Map/ShowDataLayer" -import { stat } from "fs" export class MinimapViz implements SpecialVisualization { funcName = "minimap" diff --git a/UI/Popup/MoveWizard.ts b/UI/Popup/MoveWizard.ts index d643b799e1..ee93d78f9c 100644 --- a/UI/Popup/MoveWizard.ts +++ b/UI/Popup/MoveWizard.ts @@ -54,11 +54,6 @@ export default class MoveWizard extends Toggle { options: MoveConfig ) { const t = Translations.t.move - const loginButton = new Toggle( - t.loginToMove.SetClass("btn").onClick(() => state.osmConnection.AttemptLogin()), - undefined, - state.featureSwitchUserbadge - ) const reasons: MoveReason[] = [] if (options.enableRelocation) { diff --git a/UI/Popup/SpecialTranslation.svelte b/UI/Popup/SpecialTranslation.svelte new file mode 100644 index 0000000000..cb0a6674dc --- /dev/null +++ b/UI/Popup/SpecialTranslation.svelte @@ -0,0 +1,34 @@ + + +{#each specs as specpart} + {#if typeof specpart === "string"} + + {:else if $tags !== undefined } + + {/if} +{/each} diff --git a/UI/Popup/TagRenderingAnswer.svelte b/UI/Popup/TagRenderingAnswer.svelte index e69de29bb2..c25cc206c8 100644 --- a/UI/Popup/TagRenderingAnswer.svelte +++ b/UI/Popup/TagRenderingAnswer.svelte @@ -0,0 +1,34 @@ + + +{#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties(tags))} +
+ {#if trs.length === 1} + + {/if} + {#if trs.length > 1} + {#each trs as mapping} + + {/each} + {/if} +
+{/if} diff --git a/UI/Popup/TagRenderingAnswer.ts b/UI/Popup/TagRenderingAnswer.ts index 338be0c07c..75ef9fd843 100644 --- a/UI/Popup/TagRenderingAnswer.ts +++ b/UI/Popup/TagRenderingAnswer.ts @@ -6,7 +6,7 @@ import { SubstitutedTranslation } from "../SubstitutedTranslation" import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" import Combine from "../Base/Combine" import Img from "../Base/Img" -import { SpecialVisualisationState } from "../SpecialVisualization" +import { SpecialVisualizationState } from "../SpecialVisualization" /*** * Displays the correct value for a known tagrendering @@ -15,7 +15,7 @@ export default class TagRenderingAnswer extends VariableUiElement { constructor( tagsSource: UIEventSource, configuration: TagRenderingConfig, - state: SpecialVisualisationState, + state: SpecialVisualizationState, contentClasses: string = "", contentStyle: string = "", options?: { diff --git a/UI/Popup/TagRenderingMapping.svelte b/UI/Popup/TagRenderingMapping.svelte new file mode 100644 index 0000000000..c7e4966337 --- /dev/null +++ b/UI/Popup/TagRenderingMapping.svelte @@ -0,0 +1,32 @@ + + +{#if mapping.icon !== undefined} +
+ + +
+{:else if mapping.then !== undefined} + +{/if} + diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index 2732f21052..72c1f80dad 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -1,7 +1,6 @@ import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource" import Combine from "../Base/Combine" import { InputElement, ReadonlyInputElement } from "../Input/InputElement" -import ValidatedTextField from "../Input/ValidatedTextField" import { FixedInputElement } from "../Input/FixedInputElement" import { RadioButton } from "../Input/RadioButton" import { Utils } from "../../Utils" diff --git a/UI/Reviews/SingleReview.ts b/UI/Reviews/SingleReview.ts index 5e1e050d4b..b1b1c89afb 100644 --- a/UI/Reviews/SingleReview.ts +++ b/UI/Reviews/SingleReview.ts @@ -6,7 +6,6 @@ import BaseUIElement from "../BaseUIElement" import Img from "../Base/Img" import { Review } from "mangrove-reviews-typescript" import { Store } from "../../Logic/UIEventSource" -import WikidataPreviewBox from "../Wikipedia/WikidataPreviewBox" export default class SingleReview extends Combine { constructor(review: Review & { madeByLoggedInUser: Store }) { diff --git a/UI/SpecialVisualization.ts b/UI/SpecialVisualization.ts index 4e46c00080..728f51d336 100644 --- a/UI/SpecialVisualization.ts +++ b/UI/SpecialVisualization.ts @@ -2,17 +2,13 @@ import { Store, UIEventSource } from "../Logic/UIEventSource" import BaseUIElement from "./BaseUIElement" import { DefaultGuiState } from "./DefaultGuiState" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" -import FeatureSource, { - IndexedFeatureSource, - WritableFeatureSource, -} from "../Logic/FeatureSource/FeatureSource" +import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" import { OsmConnection } from "../Logic/Osm/OsmConnection" import { Changes } from "../Logic/Osm/Changes" import { MapProperties } from "../Models/MapProperties" import LayerState from "../Logic/State/LayerState" -import { Feature } from "geojson" +import { Feature, Geometry } from "geojson" import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" -import UserRelatedState from "../Logic/State/UserRelatedState" import { MangroveIdentity } from "../Logic/Web/MangroveReviews" import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" @@ -58,6 +54,8 @@ export interface SpecialVisualization { funcName: string docs: string | BaseUIElement example?: string + + structuredExamples?(): { feature: Feature>; args: string[] }[] args: { name: string; defaultValue?: string; doc: string; required?: false | boolean }[] getLayerDependencies?: (argument: string[]) => string[] @@ -68,3 +66,11 @@ export interface SpecialVisualization { feature: Feature ): BaseUIElement } + +export type RenderingSpecification = + | string + | { + func: SpecialVisualization + args: string[] + style: string + } diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index db4d1bd35c..871d49b2a5 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -3,7 +3,11 @@ import { FixedUiElement } from "./Base/FixedUiElement" import BaseUIElement from "./BaseUIElement" import Title from "./Base/Title" import Table from "./Base/Table" -import { SpecialVisualization } from "./SpecialVisualization" +import { + RenderingSpecification, + SpecialVisualization, + SpecialVisualizationState, +} from "./SpecialVisualization" import { HistogramViz } from "./Popup/HistogramViz" import { StealViz } from "./Popup/StealViz" import { MinimapViz } from "./Popup/MinimapViz" @@ -51,10 +55,97 @@ import FeatureReviews from "../Logic/Web/MangroveReviews" import Maproulette from "../Logic/Maproulette" import SvelteUIElement from "./Base/SvelteUIElement" import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" +import { Feature } from "geojson" export default class SpecialVisualizations { public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList() + /** + * + * For a given string, returns a specification what parts are fixed and what parts are special renderings. + * Note that _normal_ substitutions are ignored. + * + * // Return empty list on empty input + * SubstitutedTranslation.ExtractSpecialComponents("") // => [] + * + * // Advanced cases with commas, braces and newlines should be handled without problem + * const templates = SubstitutedTranslation.ExtractSpecialComponents("{send_email(&LBRACEemail&RBRACE,Broken bicycle pump,Hello&COMMA\n\nWith this email&COMMA I'd like to inform you that the bicycle pump located at https://mapcomplete.osm.be/cyclofix?lat=&LBRACE_lat&RBRACE&lon=&LBRACE_lon&RBRACE&z=18#&LBRACEid&RBRACE is broken.\n\n Kind regards,Report this bicycle pump as broken)}") + * const templ = templates[0] + * templ.special.func.funcName // => "send_email" + * templ.special.args[0] = "{email}" + */ + public static constructSpecification( + template: string, + extraMappings: SpecialVisualization[] = [] + ): RenderingSpecification[] { + if (template === "") { + return [] + } + + const allKnownSpecials = extraMappings.concat(SpecialVisualizations.specialVisualizations) + for (const knownSpecial of allKnownSpecials) { + // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way' + const matched = template.match( + new RegExp(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`, "s") + ) + if (matched != null) { + // We found a special component that should be brought to live + const partBefore = SpecialVisualizations.constructSpecification( + matched[1], + extraMappings + ) + const argument = matched[2].trim() + const style = matched[3]?.substring(1) ?? "" + const partAfter = SpecialVisualizations.constructSpecification( + matched[4], + extraMappings + ) + const args = knownSpecial.args.map((arg) => arg.defaultValue ?? "") + if (argument.length > 0) { + const realArgs = argument.split(",").map((str) => + str + .trim() + .replace(/&LPARENS/g, "(") + .replace(/&RPARENS/g, ")") + .replace(/&LBRACE/g, "{") + .replace(/&RBRACE/g, "}") + .replace(/&COMMA/g, ",") + ) + for (let i = 0; i < realArgs.length; i++) { + if (args.length <= i) { + args.push(realArgs[i]) + } else { + args[i] = realArgs[i] + } + } + } + + const element: RenderingSpecification = { + args: args, + style: style, + func: knownSpecial, + } + return [...partBefore, element, ...partAfter] + } + } + + // Let's to a small sanity check to help the theme designers: + if (template.search(/{[^}]+\([^}]*\)}/) >= 0) { + // Hmm, we might have found an invalid rendering name + console.warn( + "Found a suspicious special rendering value in: ", + template, + " did you mean one of: " + /*SpecialVisualizations.specialVisualizations + .map((sp) => sp.funcName + "()") + .join(", ")*/ + ) + } + + // IF we end up here, no changes have to be made - except to remove any resting {} + return [template] + } + public static DocumentationFor(viz: string | SpecialVisualization): BaseUIElement | undefined { if (typeof viz === "string") { viz = SpecialVisualizations.specialVisualizations.find((sv) => sv.funcName === viz) @@ -649,7 +740,7 @@ export default class SpecialVisualizations { defaultValue: "mr_taskId", }, ], - constr: (state, tagsSource, args, guistate) => { + constr: (state, tagsSource, args) => { let [message, image, message_closed, status, maproulette_id_key] = args if (image === "") { image = "confirm" @@ -720,7 +811,7 @@ export default class SpecialVisualizations { funcName: "statistics", docs: "Show general statistics about the elements currently in view. Intended to use on the `current_view`-layer", args: [], - constr: (state, tagsSource, args, guiState) => { + constr: (state) => { return new Combine( state.layout.layers .filter((l) => l.name !== null) @@ -852,4 +943,23 @@ export default class SpecialVisualizations { return specialVisualizations } + + // noinspection JSUnusedGlobalSymbols + public static renderExampleOfSpecial( + state: SpecialVisualizationState, + s: SpecialVisualization + ): BaseUIElement { + const examples = + s.structuredExamples === undefined + ? [] + : s.structuredExamples().map((e) => { + return s.constr( + state, + new UIEventSource>(e.feature.properties), + e.args, + e.feature + ) + }) + return new Combine([new Title(s.funcName), s.docs, ...examples]) + } } diff --git a/UI/SubstitutedTranslation.ts b/UI/SubstitutedTranslation.ts index 65e4cd0775..b14348ccce 100644 --- a/UI/SubstitutedTranslation.ts +++ b/UI/SubstitutedTranslation.ts @@ -7,10 +7,10 @@ import { Utils } from "../Utils" import { VariableUiElement } from "./Base/VariableUIElement" import Combine from "./Base/Combine" import BaseUIElement from "./BaseUIElement" -import { DefaultGuiState } from "./DefaultGuiState" -import FeaturePipelineState from "../Logic/State/FeaturePipelineState" import LinkToWeblate from "./Base/LinkToWeblate" import { SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization" +import SpecialVisualizations from "./SpecialVisualizations" +import { Feature } from "geojson" export class SubstitutedTranslation extends VariableUiElement { public constructor( @@ -21,10 +21,10 @@ export class SubstitutedTranslation extends VariableUiElement { string, | BaseUIElement | (( - state: FeaturePipelineState, + state: SpecialVisualizationState, tagSource: UIEventSource>, argument: string[], - guistate: DefaultGuiState + feature: Feature ) => BaseUIElement) > = undefined ) { @@ -55,19 +55,23 @@ export class SubstitutedTranslation extends VariableUiElement { txt = txt.replace(new RegExp(`{${key}}`, "g"), `{${key}()}`) }) - const allElements = SubstitutedTranslation.ExtractSpecialComponents( + const allElements = SpecialVisualizations.constructSpecification( txt, extraMappings ).map((proto) => { - if (proto.fixed !== undefined) { + if (typeof proto === "string") { if (tagsSource === undefined) { - return Utils.SubstituteKeys(proto.fixed, undefined) + return Utils.SubstituteKeys(proto, undefined) } return new VariableUiElement( - tagsSource.map((tags) => Utils.SubstituteKeys(proto.fixed, tags)) + tagsSource.map((tags) => Utils.SubstituteKeys(proto, tags)) ) } - const viz = proto.special + const viz: { + func: SpecialVisualization + args: string[] + style: string + } = proto if (viz === undefined) { console.error( "SPECIALRENDERING UNDEFINED for", @@ -77,9 +81,12 @@ export class SubstitutedTranslation extends VariableUiElement { return undefined } try { + const feature = state.indexedFeatures.featuresById.data.get( + tagsSource.data.id + ) return viz.func - .constr(state, tagsSource, proto.special.args) - ?.SetStyle(proto.special.style) + .constr(state, tagsSource, proto.args, feature) + ?.SetStyle(proto.style) } catch (e) { console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e) return new FixedUiElement( @@ -97,98 +104,4 @@ export class SubstitutedTranslation extends VariableUiElement { this.SetClass("w-full") } - - /** - * - * // Return empty list on empty input - * SubstitutedTranslation.ExtractSpecialComponents("") // => [] - * - * // Advanced cases with commas, braces and newlines should be handled without problem - * const templates = SubstitutedTranslation.ExtractSpecialComponents("{send_email(&LBRACEemail&RBRACE,Broken bicycle pump,Hello&COMMA\n\nWith this email&COMMA I'd like to inform you that the bicycle pump located at https://mapcomplete.osm.be/cyclofix?lat=&LBRACE_lat&RBRACE&lon=&LBRACE_lon&RBRACE&z=18#&LBRACEid&RBRACE is broken.\n\n Kind regards,Report this bicycle pump as broken)}") - * const templ = templates[0] - * templ.special.func.funcName // => "send_email" - * templ.special.args[0] = "{email}" - */ - public static ExtractSpecialComponents( - template: string, - extraMappings: SpecialVisualization[] = [] - ): { - fixed?: string - special?: { - func: SpecialVisualization - args: string[] - style: string - } - }[] { - if (template === "") { - return [] - } - - for (const knownSpecial of extraMappings.concat( - [] // TODO enable SpecialVisualizations.specialVisualizations - )) { - // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way' - const matched = template.match( - new RegExp(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`, "s") - ) - if (matched != null) { - // We found a special component that should be brought to live - const partBefore = SubstitutedTranslation.ExtractSpecialComponents( - matched[1], - extraMappings - ) - const argument = matched[2].trim() - const style = matched[3]?.substring(1) ?? "" - const partAfter = SubstitutedTranslation.ExtractSpecialComponents( - matched[4], - extraMappings - ) - const args = knownSpecial.args.map((arg) => arg.defaultValue ?? "") - if (argument.length > 0) { - const realArgs = argument.split(",").map((str) => - str - .trim() - .replace(/&LPARENS/g, "(") - .replace(/&RPARENS/g, ")") - .replace(/&LBRACE/g, "{") - .replace(/&RBRACE/g, "}") - .replace(/&COMMA/g, ",") - ) - for (let i = 0; i < realArgs.length; i++) { - if (args.length <= i) { - args.push(realArgs[i]) - } else { - args[i] = realArgs[i] - } - } - } - - let element - element = { - special: { - args: args, - style: style, - func: knownSpecial, - }, - } - return [...partBefore, element, ...partAfter] - } - } - - // Let's to a small sanity check to help the theme designers: - if (template.search(/{[^}]+\([^}]*\)}/) >= 0) { - // Hmm, we might have found an invalid rendering name - console.warn( - "Found a suspicious special rendering value in: ", - template, - " did you mean one of: " - /*SpecialVisualizations.specialVisualizations - .map((sp) => sp.funcName + "()") - .join(", ")*/ - ) - } - - // IF we end up here, no changes have to be made - except to remove any resting {} - return [{ fixed: template }] - } } diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 90daaae917..63018f0c91 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -20,6 +20,7 @@ import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui"; import Translations from "./i18n/Translations"; import { MenuIcon } from "@rgossiaux/svelte-heroicons/solid"; + import Tr from "./Base/Tr.svelte"; export let layout: LayoutConfig; const state = new ThemeViewState(layout); @@ -48,7 +49,7 @@
- {layout.title} +
@@ -58,9 +59,7 @@
- state.guistate.filterViewIsOpened.setData(true)}> - - +
@@ -86,17 +85,6 @@
- -
-
state.guistate.filterViewIsOpened.setData(false)}>Close
- - {#each layout.layers as layer} - - {/each} - - -
-
@@ -105,31 +93,47 @@
state.guistate.welcomeMessageIsOpened.setData(false)}>Close
- selected ? "tab-selected" : "tab-unselected"}>About - selected ? "tab-selected" : "tab-unselected"}>Tab 2 + selected ? "tab-selected" : "tab-unselected"}> + + + selected ? "tab-selected" : "tab-unselected"}> + + selected ? "tab-selected" : "tab-unselected"}>Tab 3 - layout.description}> - {Translations.t.general.welcomeExplanation.general} + + {#if layout.layers.some((l) => l.presets?.length > 0)} - {Translations.t.general.welcomeExplanation.addNew} + {/if} - layout.descriptionTail}> +
- +
- +
- Content 2 + +
+ + {#each layout.layers as layer} + + {/each} + + +
+
Content 3
@@ -163,15 +167,14 @@
- +{#if $selectedElement !== undefined && $selectedLayer !== undefined}
- +
-
- +{/if} - - - - -
Loading...
- - - - - - diff --git a/import_helper.html b/import_helper.html index 9cd6cd73dc..4280fdc73f 100644 --- a/import_helper.html +++ b/import_helper.html @@ -44,7 +44,6 @@
Loading...
- diff --git a/import_viewer.html b/import_viewer.html index d53431d566..77ace0f672 100644 --- a/import_viewer.html +++ b/import_viewer.html @@ -39,7 +39,6 @@
Loading...
- From 466dd165688739fe6900dfdb63e8e788e6ee0225 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 14 Apr 2023 00:12:15 +0200 Subject: [PATCH 030/257] Fix contributor count --- Logic/BBox.ts | 11 +++++++++-- Logic/FeatureSource/Actors/GeoIndexedStore.ts | 13 ++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Logic/BBox.ts b/Logic/BBox.ts index 3a1dc9e34d..32f02acfae 100644 --- a/Logic/BBox.ts +++ b/Logic/BBox.ts @@ -225,10 +225,17 @@ export class BBox { ] } - public asGeoJson(properties: T): Feature { + public asGeojsonCached() { + if (this["geojsonCache"] === undefined) { + this["geojsonCache"] = this.asGeoJson({}) + } + return this["geojsonCache"] + } + + public asGeoJson(properties?: T): Feature { return { type: "Feature", - properties, + properties: properties, geometry: this.asGeometry(), } } diff --git a/Logic/FeatureSource/Actors/GeoIndexedStore.ts b/Logic/FeatureSource/Actors/GeoIndexedStore.ts index 39eb02f58a..a4f91eb332 100644 --- a/Logic/FeatureSource/Actors/GeoIndexedStore.ts +++ b/Logic/FeatureSource/Actors/GeoIndexedStore.ts @@ -25,11 +25,22 @@ export default class GeoIndexedStore implements FeatureSource { */ public GetFeaturesWithin(bbox: BBox): Feature[] { // TODO optimize - const bboxFeature = bbox.asGeoJson({}) + const bboxFeature = bbox.asGeojsonCached() return this.features.data.filter((f) => { if (f.geometry.type === "Point") { return bbox.contains(<[number, number]>f.geometry.coordinates) } + if (f.geometry.type === "LineString") { + const intersection = GeoOperations.intersect( + BBox.get(f).asGeojsonCached(), + bboxFeature + ) + return intersection !== undefined + } + if (f.geometry.type === "Polygon" || f.geometry.type === "MultiPolygon") { + return GeoOperations.intersect(f, bboxFeature) !== undefined + } + console.log("Calculating intersection between", bboxFeature, "and", f) return GeoOperations.intersect(f, bboxFeature) !== undefined }) } From 8a1f0599d9751fc14c1a0bab4f7af2de375e701a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 14 Apr 2023 02:42:57 +0200 Subject: [PATCH 031/257] Refactoring: fix 'delete' and 'move'-buttons as special elements --- Models/MenuState.ts | 4 +- Models/ThemeConfig/Conversion/PrepareLayer.ts | 84 +++++++++++++++++-- Models/ThemeConfig/Conversion/PrepareTheme.ts | 56 +------------ .../ThemeConfig/Conversion/ValidationUtils.ts | 15 ++++ Models/ThemeConfig/LayerConfig.ts | 6 +- Models/ThemeViewState.ts | 2 +- UI/BigComponents/Geosearch.svelte | 62 +++++++------- UI/Image/ImageUploadFlow.ts | 4 +- UI/InputElement/Helpers/LocationInput.svelte | 6 +- UI/Map/ShowDataLayer.ts | 26 +++++- UI/Popup/DeleteWizard.ts | 47 +++++------ UI/Popup/FeatureInfoBox.ts | 80 +----------------- UI/Popup/MoveWizard.ts | 78 ++++++----------- UI/Popup/SplitRoadWizard.ts | 25 +----- .../TagRendering/TagRenderingEditable.svelte | 31 ++++--- UI/SpecialVisualizations.ts | 75 ++++++++++++++++- UI/ThemeViewGUI.svelte | 2 +- assets/layers/last_click/last_click.json | 8 +- .../mapcomplete-changes.json | 2 +- 19 files changed, 317 insertions(+), 296 deletions(-) diff --git a/Models/MenuState.ts b/Models/MenuState.ts index c06a13e6eb..aa737c2fd1 100644 --- a/Models/MenuState.ts +++ b/Models/MenuState.ts @@ -38,9 +38,9 @@ export class MenuState { [], (str) => MenuState._menuviewTabs.indexOf(str) ) - this.themeIsOpened.addCallbackAndRun((isOpen) => { + this.menuIsOpened.addCallbackAndRun((isOpen) => { if (!isOpen) { - this.highlightedLayerInFilters.setData(undefined) + this.highlightedUserSetting.setData(undefined) } }) this.themeViewTab.addCallbackAndRun((tab) => { diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index cf3d7b0c62..680dc166d5 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -234,9 +234,9 @@ class ExpandTagRendering extends Conversion< if (typeof layer.source !== "string") { if (found.condition === undefined) { - found.condition = layer.source.osmTags + found.condition = layer.source["osmTags"] } else { - found.condition = { and: [found.condition, layer.source.osmTags] } + found.condition = { and: [found.condition, layer.source["osmTags"]] } } } } @@ -436,7 +436,7 @@ class DetectInline extends DesugaringStep { if (typeof json.render === "string") { spec = { "*": json.render } } else { - spec = json.render + spec = >json.render } const errors: string[] = [] for (const key in spec) { @@ -480,7 +480,10 @@ export class AddQuestionBox extends DesugaringStep { json: LayerConfigJson, context: string ): { result: LayerConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } { - if (json.tagRenderings === undefined) { + if ( + json.tagRenderings === undefined || + json.tagRenderings.some((tr) => tr["id"] === "leftover-questions") + ) { return { result: json } } json = JSON.parse(JSON.stringify(json)) @@ -500,7 +503,6 @@ export class AddQuestionBox extends DesugaringStep { const errors: string[] = [] const warnings: string[] = [] if (noLabels.length > 1) { - console.log(json.tagRenderings) errors.push( "At " + context + @@ -572,6 +574,45 @@ export class AddQuestionBox extends DesugaringStep { } } +export class AddEditingElements extends DesugaringStep { + constructor() { + super( + "Add some editing elements, such as the delete button or the move button if they are configured. These used to be handled by the feature info box, but this has been replaced by special visualisation elements", + [], + "AddEditingElements" + ) + } + + convert( + json: LayerConfigJson, + context: string + ): { result: LayerConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } { + json = JSON.parse(JSON.stringify(json)) + + if (json.allowSplit && !ValidationUtils.hasSpecialVisualisation(json, "split_button")) { + json.tagRenderings.push({ + id: "split-button", + render: { "*": "{split_button()}" }, + }) + } + + if (json.allowMove && !ValidationUtils.hasSpecialVisualisation(json, "move_button")) { + json.tagRenderings.push({ + id: "move-button", + render: { "*": "{move_button()}" }, + }) + } + if (json.deletion && !ValidationUtils.hasSpecialVisualisation(json, "delete_button")) { + json.tagRenderings.push({ + id: "delete-button", + render: { "*": "{delete_button()}" }, + }) + } + + return { result: json } + } +} + export class ExpandRewrite extends Conversion, T[]> { constructor() { super("Applies a rewrite", [], "ExpandRewrite") @@ -1064,6 +1105,36 @@ class PreparePointRendering extends Fuse { + private readonly _state: DesugaringContext + + constructor(state: DesugaringContext) { + super( + "Adds a default 'minimap'-element to the tagrenderings if none of the elements define such a minimap", + ["tagRenderings"], + "AddMiniMap" + ) + this._state = state + } + + convert(layerConfig: LayerConfigJson, context: string): { result: LayerConfigJson } { + if (!layerConfig.tagRenderings) { + return { result: layerConfig } + } + const state = this._state + const hasMinimap = ValidationUtils.hasSpecialVisualisation(layerConfig, "minimap") + if (!hasMinimap) { + layerConfig = { ...layerConfig } + layerConfig.tagRenderings = [...layerConfig.tagRenderings] + layerConfig.tagRenderings.push(state.tagRenderings.get("minimap")) + } + + return { + result: layerConfig, + } + } +} + export class PrepareLayer extends Fuse { constructor(state: DesugaringContext) { super( @@ -1072,6 +1143,9 @@ export class PrepareLayer extends Fuse { new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), new On("tagRenderings", (layer) => new Concat(new ExpandTagRendering(state, layer))), new On("tagRenderings", new Each(new DetectInline())), + new AddQuestionBox(), + new AddMiniMap(state), + new AddEditingElements(), new On("mapRendering", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), new On<(PointRenderingConfigJson | LineRenderingConfigJson)[], LayerConfigJson>( "mapRendering", diff --git a/Models/ThemeConfig/Conversion/PrepareTheme.ts b/Models/ThemeConfig/Conversion/PrepareTheme.ts index c9fb286b46..8ef58830aa 100644 --- a/Models/ThemeConfig/Conversion/PrepareTheme.ts +++ b/Models/ThemeConfig/Conversion/PrepareTheme.ts @@ -10,7 +10,7 @@ import { SetDefault, } from "./Conversion" import { LayoutConfigJson } from "../Json/LayoutConfigJson" -import { AddQuestionBox, PrepareLayer } from "./PrepareLayer" +import { PrepareLayer } from "./PrepareLayer" import { LayerConfigJson } from "../Json/LayerConfigJson" import { Utils } from "../../../Utils" import Constants from "../../Constants" @@ -295,56 +295,6 @@ class AddImportLayers extends DesugaringStep { } } -export class AddMiniMap extends DesugaringStep { - private readonly _state: DesugaringContext - - constructor(state: DesugaringContext) { - super( - "Adds a default 'minimap'-element to the tagrenderings if none of the elements define such a minimap", - ["tagRenderings"], - "AddMiniMap" - ) - this._state = state - } - - /** - * Returns true if this tag rendering has a minimap in some language. - * Note: this minimap can be hidden by conditions - * - * AddMiniMap.hasMinimap({render: "{minimap()}"}) // => true - * AddMiniMap.hasMinimap({render: {en: "{minimap()}"}}) // => true - * AddMiniMap.hasMinimap({render: {en: "{minimap()}", nl: "{minimap()}"}}) // => true - * AddMiniMap.hasMinimap({render: {en: "{minimap()}", nl: "No map for the dutch!"}}) // => true - * AddMiniMap.hasMinimap({render: "{minimap()}"}) // => true - * AddMiniMap.hasMinimap({render: "{minimap(18,featurelist)}"}) // => true - * AddMiniMap.hasMinimap({mappings: [{if: "xyz=abc",then: "{minimap(18,featurelist)}"}]}) // => true - * AddMiniMap.hasMinimap({render: "Some random value {key}"}) // => false - * AddMiniMap.hasMinimap({render: "Some random value {minimap}"}) // => false - */ - static hasMinimap(renderingConfig: TagRenderingConfigJson): boolean { - return ValidationUtils.getSpecialVisualisations(renderingConfig).some( - (vis) => vis.funcName === "minimap" - ) - } - - convert(layerConfig: LayerConfigJson, context: string): { result: LayerConfigJson } { - const state = this._state - const hasMinimap = - layerConfig.tagRenderings?.some((tr) => - AddMiniMap.hasMinimap(tr) - ) ?? true - if (!hasMinimap) { - layerConfig = { ...layerConfig } - layerConfig.tagRenderings = [...layerConfig.tagRenderings] - layerConfig.tagRenderings.push(state.tagRenderings.get("minimap")) - } - - return { - result: layerConfig, - } - } -} - class AddContextToTranslationsInLayout extends DesugaringStep { constructor() { super( @@ -660,9 +610,7 @@ export class PrepareTheme extends Fuse { ? new Pass("AddDefaultLayers is disabled due to the set flag") : new AddDefaultLayers(state), new AddDependencyLayersToTheme(state), - new AddImportLayers(), - new On("layers", new Each(new AddQuestionBox())), - new On("layers", new Each(new AddMiniMap(state))) + new AddImportLayers() ) } diff --git a/Models/ThemeConfig/Conversion/ValidationUtils.ts b/Models/ThemeConfig/Conversion/ValidationUtils.ts index 036d84185a..43cd75c825 100644 --- a/Models/ThemeConfig/Conversion/ValidationUtils.ts +++ b/Models/ThemeConfig/Conversion/ValidationUtils.ts @@ -2,8 +2,22 @@ import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" import { Utils } from "../../../Utils" import SpecialVisualizations from "../../../UI/SpecialVisualizations" import { RenderingSpecification, SpecialVisualization } from "../../../UI/SpecialVisualization" +import { LayerConfigJson } from "../Json/LayerConfigJson" export default class ValidationUtils { + public static hasSpecialVisualisation( + layer: LayerConfigJson, + specialVisualisation: string + ): boolean { + return ( + layer.tagRenderings?.some((tagRendering) => + ValidationUtils.getSpecialVisualisations(tagRendering).some( + (vis) => vis.funcName === specialVisualisation + ) + ) ?? false + ) + } + /** * Gives all the (function names of) used special visualisations * @param renderingConfig @@ -15,6 +29,7 @@ export default class ValidationUtils { (spec) => spec["func"] ) } + public static getSpecialVisualsationsWithArgs( renderingConfig: TagRenderingConfigJson ): RenderingSpecification[] { diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index 459d60f9ba..bedbf65de5 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -90,7 +90,7 @@ export default class LayerConfig extends WithContextLoader { if (json.source === "special" || json.source === "special:library") { this.source = null - } else if (json.source.osmTags === undefined) { + } else if (json.source["osmTags"] === undefined) { throw ( "Layer " + this.id + @@ -122,8 +122,8 @@ export default class LayerConfig extends WithContextLoader { } this.syncSelection = json.syncSelection ?? "no" if (typeof json.source !== "string") { - this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30 - const osmTags = TagUtils.Tag(json.source.osmTags, context + "source.osmTags") + this.maxAgeOfCache = json.source["maxCacheAge"] ?? 24 * 60 * 60 * 30 + const osmTags = TagUtils.Tag(json.source["osmTags"], context + "source.osmTags") if (osmTags.isNegative()) { throw ( context + diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 6023a0dda1..b63508ed46 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -248,7 +248,7 @@ export default class ThemeViewState implements SpecialVisualizationState { new UIEventSource>(last_click.properties) ) new ShowDataLayer(this.map, { - features: last_click, + features: new FilteringFeatureSource(last_click_layer, last_click), doShowLayer: new ImmutableStore(true), layer: last_click_layer.layerDef, selectedElement: this.selectedElement, diff --git a/UI/BigComponents/Geosearch.svelte b/UI/BigComponents/Geosearch.svelte index 502387b30f..10965d13f1 100644 --- a/UI/BigComponents/Geosearch.svelte +++ b/UI/BigComponents/Geosearch.svelte @@ -10,56 +10,58 @@ import Hotkeys from "../Base/Hotkeys"; import { Geocoding } from "../../Logic/Osm/Geocoding"; import { BBox } from "../../Logic/BBox"; - import type { SpecialVisualizationState } from "../SpecialVisualization"; + import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore"; - export let state: SpecialVisualizationState - export let bounds: UIEventSource - export let selectedElement: UIEventSource; - export let selectedLayer: UIEventSource; + export let perLayer: ReadonlyMap | undefined = undefined; + export let bounds: UIEventSource; + export let selectedElement: UIEventSource | undefined = undefined; + export let selectedLayer: UIEventSource | undefined = undefined; let searchContents: string = undefined; let isRunning: boolean = false; let inputElement: HTMLInputElement; - - let feedback: string = undefined + + let feedback: string = undefined; Hotkeys.RegisterHotkey( { ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => { - inputElement?.focus() - inputElement?.select() + inputElement?.focus(); + inputElement?.select(); } - ) + ); + async function performSearch() { try { isRunning = true; - searchContents = searchContents?.trim() ?? "" + searchContents = searchContents?.trim() ?? ""; if (searchContents === "") { - return + return; } - const result = await Geocoding.Search(searchContents, bounds.data) + const result = await Geocoding.Search(searchContents, bounds.data); if (result.length == 0) { - feedback = Translations.t.search.nothing.txt - return + feedback = Translations.t.search.nothing.txt; + return; } - const poi = result[0] - const [lat0, lat1, lon0, lon1] = poi.boundingbox - bounds.set(new BBox([[lon0, lat0], [lon1, lat1]]).pad(0.01)) - const id = poi.osm_type + "/" + poi.osm_id - const perLayer = state.perLayer - const layers = Array.from(perLayer.values()) - for (const layer of layers) { - const found = layer.features.data.find(f => f.properties.id === id) - selectedElement.setData(found) - selectedLayer.setData(layer.layer.layerDef) - + const poi = result[0]; + const [lat0, lat1, lon0, lon1] = poi.boundingbox; + bounds.set(new BBox([[lon0, lat0], [lon1, lat1]]).pad(0.01)); + if (perLayer !== undefined) { + const id = poi.osm_type + "/" + poi.osm_id; + const layers = Array.from(perLayer?.values() ?? []); + for (const layer of layers) { + const found = layer.features.data.find(f => f.properties.id === id); + selectedElement?.setData(found); + selectedLayer?.setData(layer.layer.layerDef); + + } } - }catch (e) { - console.error(e) - feedback = Translations.t.search.error.txt + } catch (e) { + console.error(e); + feedback = Translations.t.search.error.txt; } finally { isRunning = false; } @@ -72,7 +74,7 @@ {#if isRunning} {Translations.t.general.search.searching} - {:else if feedback !== undefined} + {:else if feedback !== undefined}
feedback = undefined}> {feedback}
diff --git a/UI/Image/ImageUploadFlow.ts b/UI/Image/ImageUploadFlow.ts index 30842a14ad..a2457bf078 100644 --- a/UI/Image/ImageUploadFlow.ts +++ b/UI/Image/ImageUploadFlow.ts @@ -177,9 +177,7 @@ export class ImageUploadFlow extends Toggle { ) .onClick(() => { console.log("Opening the license settings... ") - ScrollableFullScreen.collapse() - DefaultGuiState.state.userInfoIsOpened.setData(true) - DefaultGuiState.state.userInfoFocusedQuestion.setData("picture-license") + state.guistate.openUsersettings("picture-license") }) .SetClass("underline"), ]).SetStyle("font-size:small;"), diff --git a/UI/InputElement/Helpers/LocationInput.svelte b/UI/InputElement/Helpers/LocationInput.svelte index 7c88500c87..9ca0175f26 100644 --- a/UI/InputElement/Helpers/LocationInput.svelte +++ b/UI/InputElement/Helpers/LocationInput.svelte @@ -4,8 +4,6 @@ import { Map as MlMap } from "maplibre-gl"; import { MapLibreAdaptor } from "../../Map/MapLibreAdaptor"; import MaplibreMap from "../../Map/MaplibreMap.svelte"; - import Svg from "../../../Svg"; - import ToSvelte from "../../Base/ToSvelte.svelte"; import DragInvitation from "../../Base/DragInvitation.svelte"; /** @@ -14,13 +12,13 @@ export let value: UIEventSource<{lon: number, lat: number}>; export let mapProperties: Partial & { readonly location: UIEventSource<{ lon: number; lat: number }> } = undefined; /** - * Called when setup is done, cna be used to add layrs to the map + * Called when setup is done, can be used to add more layers to the map */ export let onCreated : (value: Store<{lon: number, lat: number}> , map: Store, mapProperties: MapProperties ) => void export let map: UIEventSource = new UIEventSource(undefined); let mla = new MapLibreAdaptor(map, mapProperties); - + mapProperties.location.syncWith(value) if(onCreated){ onCreated(value, map, mla) } diff --git a/UI/Map/ShowDataLayer.ts b/UI/Map/ShowDataLayer.ts index c3306b262a..25f863cdfd 100644 --- a/UI/Map/ShowDataLayer.ts +++ b/UI/Map/ShowDataLayer.ts @@ -8,7 +8,7 @@ import PointRenderingConfig from "../../Models/ThemeConfig/PointRenderingConfig" import { OsmTags } from "../../Models/OsmFeature" import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource" import { BBox } from "../../Logic/BBox" -import { Feature } from "geojson" +import { Feature, Point } from "geojson" import ScrollableFullScreen from "../Base/ScrollableFullScreen" import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" import { Utils } from "../../Utils" @@ -26,6 +26,7 @@ class PointRenderingLayer { private readonly _onClick: (feature: Feature) => void private readonly _allMarkers: Map = new Map() private _dirty = false + constructor( map: MlMap, features: FeatureSource, @@ -139,6 +140,18 @@ class PointRenderingLayer { store .map((tags) => this._config.rotationAlignment.GetRenderValue(tags).Subs(tags).txt) .addCallbackAndRun((pitchAligment) => marker.setRotationAlignment(pitchAligment)) + if (feature.geometry.type === "Point") { + // When the tags get 'pinged', check that the location didn't change + store.addCallbackAndRunD(() => { + // Check if the location is still the same + const oldLoc = marker.getLngLat() + const newloc = (feature.geometry).coordinates + if (newloc[0] === oldLoc.lng && newloc[1] === oldLoc.lat) { + return + } + marker.setLngLat({ lon: newloc[0], lat: newloc[1] }) + }) + } return marker } } @@ -159,6 +172,7 @@ class LineRenderingLayer { private static readonly lineConfigKeysColor = ["color", "fillColor"] as const private static readonly lineConfigKeysNumber = ["width", "offset"] as const + private static missingIdTriggered = false private readonly _map: MlMap private readonly _config: LineRenderingConfig private readonly _visibility?: Store @@ -167,7 +181,6 @@ class LineRenderingLayer { private readonly _layername: string private readonly _listenerInstalledOn: Set = new Set() - private static missingIdTriggered = false constructor( map: MlMap, features: FeatureSource, @@ -248,6 +261,11 @@ class LineRenderingLayer { }, }) + map.on("click", linelayer, (e) => { + console.log("Click", e) + e.originalEvent["consumed"] = true + this._onClick(e.features[0]) + }) const polylayer = this._layername + "_polygon" map.addLayer({ source: this._layername, @@ -260,6 +278,10 @@ class LineRenderingLayer { "fill-opacity": 0.1, }, }) + map.on("click", polylayer, (e) => { + e.originalEvent["consumed"] = true + this._onClick(e.features[0]) + }) this._visibility?.addCallbackAndRunD((visible) => { try { diff --git a/UI/Popup/DeleteWizard.ts b/UI/Popup/DeleteWizard.ts index 13e7360875..84f0d552ed 100644 --- a/UI/Popup/DeleteWizard.ts +++ b/UI/Popup/DeleteWizard.ts @@ -20,10 +20,10 @@ import { RadioButton } from "../Input/RadioButton" import { FixedInputElement } from "../Input/FixedInputElement" import Title from "../Base/Title" import { SubstitutedTranslation } from "../SubstitutedTranslation" -import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import TagRenderingQuestion from "./TagRenderingQuestion" -import { OsmId } from "../../Models/OsmFeature" +import { OsmId, OsmTags } from "../../Models/OsmFeature" import { LoginToggle } from "./LoginButton" +import { SpecialVisualizationState } from "../SpecialVisualization" export default class DeleteWizard extends Toggle { /** @@ -41,13 +41,18 @@ export default class DeleteWizard extends Toggle { * Ideal for the case of "THIS PATH IS ON MY GROUND AND SHOULD BE DELETED IMMEDIATELY OR I WILL GET MY LAWYER" but to mark it as private instead. * (Note that _delete_reason is used as trigger to do actual deletion - setting such a tag WILL delete from the database with that as changeset comment) * - * @param id: The id of the element to remove - * @param state: the state of the application - * @param options softDeletionTags: the tags to apply if the user doesn't have permission to delete, e.g. 'disused:amenity=public_bookcase', 'amenity='. After applying, the element should not be picked up on the map anymore. If undefined, the wizard will only show up if the point can be (hard) deleted */ - constructor(id: OsmId, state: FeaturePipelineState, options: DeleteConfig) { - const deleteAbility = new DeleteabilityChecker(id, state, options.neededChangesets) - const tagsSource = state.allElements.getEventSourceById(id) + constructor( + id: OsmId, + tagsSource: UIEventSource, + state: SpecialVisualizationState, + options: DeleteConfig + ) { + const deleteAbility = new DeleteabilityChecker( + id, + state.osmConnection, + options.neededChangesets + ) const isDeleted = new UIEventSource(false) const allowSoftDeletion = !!options.softDeletionTags @@ -62,7 +67,7 @@ export default class DeleteWizard extends Toggle { if (selected["retagTo"] !== undefined) { // no _delete_reason is given, which implies that this is _not_ a deletion but merely a retagging via a nonDeleteMapping actionToTake = new ChangeTagAction(id, selected["retagTo"], tagsSource.data, { - theme: state?.layoutToUse?.id ?? "unkown", + theme: state?.layout?.id ?? "unkown", changeType: "special-delete", }) } else { @@ -70,7 +75,7 @@ export default class DeleteWizard extends Toggle { id, options.softDeletionTags, { - theme: state?.layoutToUse?.id ?? "unkown", + theme: state?.layout?.id ?? "unkown", specialMotivation: selected["deleteReason"], }, deleteAbility.canBeDeleted.data.canBeDeleted @@ -250,7 +255,7 @@ export default class DeleteWizard extends Toggle { private static constructMultipleChoice( config: DeleteConfig, tagsSource: UIEventSource>, - state: FeaturePipelineState + state: SpecialVisualizationState ): InputElement<{ deleteReason: string } | { retagTo: TagsFilter }> { const elements: InputElement<{ deleteReason: string } | { retagTo: TagsFilter }>[] = [] @@ -282,19 +287,13 @@ export default class DeleteWizard extends Toggle { class DeleteabilityChecker { public readonly canBeDeleted: UIEventSource<{ canBeDeleted?: boolean; reason: Translation }> - private readonly _id: string + private readonly _id: OsmId private readonly _allowDeletionAtChangesetCount: number - private readonly _state: { - osmConnection: OsmConnection - } + private readonly _osmConnection: OsmConnection - constructor( - id: string, - state: { osmConnection: OsmConnection }, - allowDeletionAtChangesetCount?: number - ) { + constructor(id: OsmId, osmConnection: OsmConnection, allowDeletionAtChangesetCount?: number) { this._id = id - this._state = state + this._osmConnection = osmConnection this._allowDeletionAtChangesetCount = allowDeletionAtChangesetCount ?? Number.MAX_VALUE this.canBeDeleted = new UIEventSource<{ canBeDeleted?: boolean; reason: Translation }>({ @@ -324,7 +323,7 @@ class DeleteabilityChecker { } // Does the currently logged in user have enough experience to delete this point? - const deletingPointsOfOtherAllowed = this._state.osmConnection.userDetails.map((ud) => { + const deletingPointsOfOtherAllowed = this._osmConnection.userDetails.map((ud) => { if (ud === undefined) { return undefined } @@ -347,10 +346,10 @@ class DeleteabilityChecker { // Not yet downloaded return null } - const userId = self._state.osmConnection.userDetails.data.uid + const userId = self._osmConnection.userDetails.data.uid return !previous.some((editor) => editor !== userId) }, - [self._state.osmConnection.userDetails] + [self._osmConnection.userDetails] ) // User allowed OR only edited by self? diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index 327d6dc63a..3efe5d9ede 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -17,7 +17,6 @@ import MoveWizard from "./MoveWizard" import Toggle from "../Input/Toggle" import Lazy from "../Base/Lazy" import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" -import { Tag } from "../../Logic/Tags/Tag" import Svg from "../../Svg" import Translations from "../i18n/Translations" @@ -32,9 +31,6 @@ export default class FeatureInfoBox extends ScrollableFullScreen { setHash?: true | boolean } ) { - if (state === undefined) { - throw "State is undefined!" - } const showAllQuestions = state.featureSwitchShowAllQuestions.map( (fsShow) => fsShow || state.showAllQuestionsAtOnce.data, [state.showAllQuestionsAtOnce] @@ -98,27 +94,11 @@ export default class FeatureInfoBox extends ScrollableFullScreen { private static GenerateMainContent( tags: UIEventSource, layerConfig: LayerConfig, - state: FeaturePipelineState, - showAllQuestions?: Store + state: FeaturePipelineState ): BaseUIElement { let questionBoxes: Map = new Map() const t = Translations.t.general const allGroupNames = Utils.Dedup(layerConfig.tagRenderings.map((tr) => tr.group)) - if (state?.featureSwitchUserbadge?.data ?? true) { - const questionSpecs = layerConfig.tagRenderings.filter((tr) => tr.id === "questions") - for (const groupName of allGroupNames) { - const questions = layerConfig.tagRenderings.filter((tr) => tr.group === groupName) - const questionSpec = questionSpecs.filter((tr) => tr.group === groupName)[0] - const questionBox = new QuestionBox(state, { - tagsSource: tags, - tagRenderings: questions, - units: layerConfig.units, - showAllQuestionsAtOnce: - questionSpec?.freeform?.helperArgs["showAllQuestions"] ?? showAllQuestions, - }) - questionBoxes.set(groupName, questionBox) - } - } const withQuestion = layerConfig.tagRenderings.filter( (tr) => tr.question !== undefined @@ -243,40 +223,6 @@ export default class FeatureInfoBox extends ScrollableFullScreen { editElements.push(questionBox) }) - if (layerConfig.allowMove) { - editElements.push( - new VariableUiElement( - tags - .map((tags) => tags.id) - .map((id) => { - const feature = state.allElements.ContainingFeatures.get(id) - if (feature === undefined) { - return "This feature is not register in the state.allElements and cannot be moved" - } - return new MoveWizard(feature, state, layerConfig.allowMove) - }) - ).SetClass("text-base") - ) - } - - if (layerConfig.deletion) { - editElements.push( - new VariableUiElement( - tags - .map((tags) => tags.id) - .map((id) => new DeleteWizard(id, state, layerConfig.deletion)) - ).SetClass("text-base") - ) - } - - if (layerConfig.allowSplit) { - editElements.push( - new VariableUiElement( - tags.map((tags) => tags.id).map((id) => new SplitRoadWizard(id, state)) - ).SetClass("text-base") - ) - } - editElements.push( new VariableUiElement( state.osmConnection.userDetails @@ -302,30 +248,6 @@ export default class FeatureInfoBox extends ScrollableFullScreen { ) ) - editElements.push( - Toggle.If(state.featureSwitchIsDebugging, () => { - const config_all_tags: TagRenderingConfig = new TagRenderingConfig( - { render: "{all_tags()}" }, - "" - ) - const config_download: TagRenderingConfig = new TagRenderingConfig( - { render: "{export_as_geojson()}" }, - "" - ) - const config_id: TagRenderingConfig = new TagRenderingConfig( - { render: "{open_in_iD()}" }, - "" - ) - - return new Combine([ - new TagRenderingAnswer(tags, config_all_tags, state), - new TagRenderingAnswer(tags, config_download, state), - new TagRenderingAnswer(tags, config_id, state), - "This is layer " + layerConfig.id, - ]) - }) - ) - return new Combine(editElements).SetClass("flex flex-col") } } diff --git a/UI/Popup/MoveWizard.ts b/UI/Popup/MoveWizard.ts index ee93d78f9c..0fba077ced 100644 --- a/UI/Popup/MoveWizard.ts +++ b/UI/Popup/MoveWizard.ts @@ -1,29 +1,28 @@ import { SubtleButton } from "../Base/SubtleButton" import Combine from "../Base/Combine" import Svg from "../../Svg" -import { OsmConnection } from "../../Logic/Osm/OsmConnection" import Toggle from "../Input/Toggle" import { UIEventSource } from "../../Logic/UIEventSource" import Translations from "../i18n/Translations" import { VariableUiElement } from "../Base/VariableUIElement" import { Translation } from "../i18n/Translation" import BaseUIElement from "../BaseUIElement" -import LocationInput from "../Input/LocationInput" -import Loc from "../../Models/Loc" import { GeoOperations } from "../../Logic/GeoOperations" import { OsmObject } from "../../Logic/Osm/OsmObject" -import { Changes } from "../../Logic/Osm/Changes" import ChangeLocationAction from "../../Logic/Osm/Actions/ChangeLocationAction" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import MoveConfig from "../../Models/ThemeConfig/MoveConfig" -import { ElementStorage } from "../../Logic/ElementStorage" -import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" -import BaseLayer from "../../Models/BaseLayer" -import SearchAndGo from "../BigComponents/SearchAndGo" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import { And } from "../../Logic/Tags/And" import { Tag } from "../../Logic/Tags/Tag" import { LoginToggle } from "./LoginButton" +import { SpecialVisualizationState } from "../SpecialVisualization" +import { Feature, Point } from "geojson" +import { OsmTags } from "../../Models/OsmFeature" +import SvelteUIElement from "../Base/SvelteUIElement" +import { MapProperties } from "../../Models/MapProperties" +import LocationInput from "../InputElement/Helpers/LocationInput.svelte" +import Geosearch from "../BigComponents/Geosearch.svelte" +import Constants from "../../Models/Constants" interface MoveReason { text: Translation | string @@ -43,14 +42,9 @@ export default class MoveWizard extends Toggle { * The UI-element which helps moving a point */ constructor( - featureToMove: any, - state: { - osmConnection: OsmConnection - featureSwitchUserbadge: UIEventSource - changes: Changes - layoutToUse: LayoutConfig - allElements: ElementStorage - }, + featureToMove: Feature, + tags: UIEventSource, + state: SpecialVisualizationState, options: MoveConfig ) { const t = Translations.t.move @@ -130,56 +124,38 @@ export default class MoveWizard extends Toggle { if (reason === undefined) { return undefined } - const loc = new UIEventSource({ - lon: lon, - lat: lat, - zoom: reason?.startZoom ?? 16, - }) - let background: string[] - if (typeof reason.background == "string") { - background = [reason.background] - } else { - background = reason.background + const mapProperties: Partial = { + minzoom: new UIEventSource(reason.minZoom), + zoom: new UIEventSource(reason?.startZoom ?? 16), + location: new UIEventSource({ lon, lat }), + bounds: new UIEventSource(undefined), } - - const preferredBackground = AvailableBaseLayers.SelectBestLayerAccordingTo( - loc, - new UIEventSource(background) - ).data - - const locationInput = new LocationInput({ - minZoom: reason.minZoom, - centerLocation: loc, - mapBackground: new UIEventSource(preferredBackground), // We detach the layer - state: state, + const value = new UIEventSource<{ lon: number; lat: number }>(undefined) + const locationInput = new SvelteUIElement(LocationInput, { + mapProperties, + value, }) - if (reason.lockBounds) { - locationInput.installBounds(0.05, true) - } - let searchPanel: BaseUIElement = undefined if (reason.includeSearch) { - searchPanel = new SearchAndGo({ - leafletMap: locationInput.leafletMap, - }) + searchPanel = new SvelteUIElement(Geosearch, { bounds: mapProperties.bounds }) } locationInput.SetStyle("height: 17.5rem") const confirmMove = new SubtleButton(Svg.move_confirm_svg(), t.confirmMove) confirmMove.onClick(async () => { - const loc = locationInput.GetValue().data + const loc = value.data await state.changes.applyAction( new ChangeLocationAction(featureToMove.properties.id, [loc.lon, loc.lat], { reason: reason.changesetCommentValue, - theme: state.layoutToUse.id, + theme: state.layout.id, }) ) featureToMove.properties._lat = loc.lat featureToMove.properties._lon = loc.lon - + featureToMove.geometry.coordinates = [loc.lon, loc.lat] if (reason.eraseAddressFields) { await state.changes.applyAction( new ChangeTagAction( @@ -193,13 +169,13 @@ export default class MoveWizard extends Toggle { featureToMove.properties, { changeType: "relocated", - theme: state.layoutToUse.id, + theme: state.layout.id, } ) ) } - state.allElements.getEventSourceById(id).ping() + state.featureProperties.getStore(id).ping() currentStep.setData("moved") }) const zoomInFurhter = t.zoomInFurther.SetClass("alert block m-6") @@ -209,7 +185,7 @@ export default class MoveWizard extends Toggle { new Toggle( confirmMove, zoomInFurhter, - locationInput.GetValue().map((l) => l.zoom >= 19) + mapProperties.zoom.map((zoom) => zoom >= Constants.minZoomLevelToAddNewPoint) ), ]).SetClass("flex flex-col") }) diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index afe508bcd7..8971b0e205 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -2,33 +2,27 @@ import Toggle from "../Input/Toggle" import Svg from "../../Svg" import { UIEventSource } from "../../Logic/UIEventSource" import { SubtleButton } from "../Base/SubtleButton" -import Minimap from "../Base/Minimap" -import ShowDataLayer from "../ShowDataLayer/ShowDataLayer" import { GeoOperations } from "../../Logic/GeoOperations" -import { LeafletMouseEvent } from "leaflet" import Combine from "../Base/Combine" import { Button } from "../Base/Button" import Translations from "../i18n/Translations" import SplitAction from "../../Logic/Osm/Actions/SplitAction" import Title from "../Base/Title" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" -import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { BBox } from "../../Logic/BBox" import split_point from "../../assets/layers/split_point/split_point.json" import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { Changes } from "../../Logic/Osm/Changes" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import { ElementStorage } from "../../Logic/ElementStorage" -import BaseLayer from "../../Models/BaseLayer" import FilteredLayer from "../../Models/FilteredLayer" import BaseUIElement from "../BaseUIElement" import { VariableUiElement } from "../Base/VariableUIElement" import ScrollableFullScreen from "../Base/ScrollableFullScreen" import { LoginToggle } from "./LoginButton" +import { SpecialVisualizationState } from "../SpecialVisualization" export default class SplitRoadWizard extends Combine { - // @ts-ignore private static splitLayerStyling = new LayerConfig( split_point, "(BUILTIN) SplitRoadWizard.ts", @@ -43,22 +37,7 @@ export default class SplitRoadWizard extends Combine { * @param id: The id of the road to remove * @param state: the state of the application */ - constructor( - id: string, - state: { - filteredLayers: UIEventSource - backgroundLayer: UIEventSource - featureSwitchIsTesting: UIEventSource - featureSwitchIsDebugging: UIEventSource - featureSwitchShowAllQuestions: UIEventSource - osmConnection: OsmConnection - featureSwitchUserbadge: UIEventSource - changes: Changes - layoutToUse: LayoutConfig - allElements: ElementStorage - selectedElement: UIEventSource - } - ) { + constructor(id: string, state: SpecialVisualizationState) { const t = Translations.t.split // Contains the points on the road that are selected to split on - contains geojson points with extra properties such as 'location' with the distance along the linestring diff --git a/UI/Popup/TagRendering/TagRenderingEditable.svelte b/UI/Popup/TagRendering/TagRenderingEditable.svelte index 33d6969d0d..f0480fff71 100644 --- a/UI/Popup/TagRendering/TagRenderingEditable.svelte +++ b/UI/Popup/TagRendering/TagRenderingEditable.svelte @@ -26,18 +26,27 @@ })); let htmlElem: HTMLElement; + const _htmlElement = new UIEventSource(undefined); + $: _htmlElement.setData(htmlElem); + + function setHighlighting() { + if (highlightedRendering === undefined) { + return; + } + if (htmlElem === undefined) { + return; + } + const highlighted = highlightedRendering.data; + if (config.id === highlighted) { + htmlElem.classList.add("glowing-shadow"); + } else { + htmlElem.classList.remove("glowing-shadow"); + } + } + if (highlightedRendering) { - $: onDestroy(highlightedRendering.addCallbackAndRun(highlighted => { - console.log("Highlighted rendering is", highlighted) - if(htmlElem === undefined){ - return - } - if (config.id === highlighted) { - htmlElem.classList.add("glowing-shadow"); - } else { - htmlElem.classList.remove("glowing-shadow"); - } - })); + onDestroy(highlightedRendering?.addCallbackAndRun(() => setHighlighting())) + onDestroy(_htmlElement.addCallbackAndRun(() => setHighlighting())) } diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 4d83b58708..652f988182 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -54,7 +54,7 @@ import Maproulette from "../Logic/Maproulette" import SvelteUIElement from "./Base/SvelteUIElement" import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" import QuestionViz from "./Popup/QuestionViz" -import { Feature } from "geojson" +import { Feature, Point } from "geojson" import { GeoOperations } from "../Logic/GeoOperations" import CreateNewNote from "./Popup/CreateNewNote.svelte" import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" @@ -76,6 +76,9 @@ import { SaveButton } from "./Popup/SaveButton" import Lazy from "./Base/Lazy" import { CheckBox } from "./Input/Checkboxes" import Slider from "./Input/Slider" +import DeleteWizard from "./Popup/DeleteWizard" +import { OsmId, OsmTags } from "../Models/OsmFeature" +import MoveWizard from "./Popup/MoveWizard" class NearbyImageVis implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -226,6 +229,7 @@ class StealViz implements SpecialVisualization { required: true, }, ] + constr(state: SpecialVisualizationState, featureTags, args) { const [featureIdKey, layerAndtagRenderingIds] = args const tagRenderings: [LayerConfig, TagRenderingConfig][] = [] @@ -273,6 +277,7 @@ class StealViz implements SpecialVisualization { return [layerId] } } + export default class SpecialVisualizations { public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList() @@ -521,6 +526,74 @@ export default class SpecialVisualizations { new HistogramViz(), new StealViz(), new MinimapViz(), + { + funcName: "split_button", + docs: "Adds a button which allows to split a way", + args: [], + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): BaseUIElement { + return new VariableUiElement( + // TODO + tagSource + .map((tags) => tags.id) + .map((id) => new FixedUiElement("TODO: enable splitting")) // new SplitRoadWizard(id, state)) + ) + }, + }, + { + funcName: "move_button", + docs: "Adds a button which allows to move the object to another location. The config will be read from the layer config", + args: [], + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): BaseUIElement { + if (feature.geometry.type !== "Point") { + return undefined + } + + return new MoveWizard( + >feature, + >tagSource, + state, + layer.allowMove + ) + }, + }, + { + funcName: "delete_button", + docs: "Adds a button which allows to delete the object at this location. The config will be read from the layer config", + args: [], + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): BaseUIElement { + return new VariableUiElement( + tagSource + .map((tags) => tags.id) + .map( + (id) => + new DeleteWizard( + id, + >tagSource, + state, + layer.deletion // Reading the configuration from the layerconfig is a bit cheating and should be factored out + ) + ) + ) + }, + }, new ShareLinkViz(), new UploadToOsmViz(), new MultiApplyViz(), diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index f42464a68c..6d2d4c50f5 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -93,7 +93,7 @@
- +
diff --git a/assets/layers/last_click/last_click.json b/assets/layers/last_click/last_click.json index 1adede9f8d..6d138a098e 100644 --- a/assets/layers/last_click/last_click.json +++ b/assets/layers/last_click/last_click.json @@ -2,6 +2,12 @@ "id": "last_click", "description": "This layer defines how to render the 'last click'-location. By default, it will show a marker with the possibility to add a new point (if there are some presets) and/or to add a new note (if the 'note' layer attribute is set). If none are possible, this layer won't show up", "source": "special", + "isShown": { + "or": [ + "has_presets=yes", + "has_note_layer=yes" + ] + }, "name": null, "titleIcons": [], "title": { @@ -153,4 +159,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index 15610e22ed..d8ec3f5b29 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -116,7 +116,7 @@ "mappings": [ { "if": "theme=advertising", - "then": "./assets/themes/advertising/poster_box.svg" + "then": "./assets/themes/advertising/icon.svg" }, { "if": "theme=aed", From ef0ec5160d050b0f49982cdd86356d774887e41d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 14 Apr 2023 04:33:06 +0200 Subject: [PATCH 032/257] Refactoring: fix download buttons --- Logic/FeatureSource/Actors/GeoIndexedStore.ts | 2 +- .../Sources/FilteringFeatureSource.ts | 20 +- Models/FilteredLayer.ts | 33 ++- Models/MenuState.ts | 2 +- UI/BigComponents/DownloadPanel.ts | 195 ++++++++---------- UI/ThemeViewGUI.svelte | 16 +- langs/en.json | 2 +- package-lock.json | 2 +- package.json | 2 +- theme.html | 2 +- 10 files changed, 142 insertions(+), 134 deletions(-) diff --git a/Logic/FeatureSource/Actors/GeoIndexedStore.ts b/Logic/FeatureSource/Actors/GeoIndexedStore.ts index a4f91eb332..314706536e 100644 --- a/Logic/FeatureSource/Actors/GeoIndexedStore.ts +++ b/Logic/FeatureSource/Actors/GeoIndexedStore.ts @@ -23,7 +23,7 @@ export default class GeoIndexedStore implements FeatureSource { * @param bbox * @constructor */ - public GetFeaturesWithin(bbox: BBox): Feature[] { + public GetFeaturesWithin(bbox: BBox, strict: boolean = false): Feature[] { // TODO optimize const bboxFeature = bbox.asGeojsonCached() return this.features.data.filter((f) => { diff --git a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts index 6750022110..2a2b52d760 100644 --- a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts @@ -54,7 +54,6 @@ export default class FilteringFeatureSource implements FeatureSource { this.update() } - private update() { const self = this const layer = this._layer @@ -64,26 +63,9 @@ export default class FilteringFeatureSource implements FeatureSource { const newFeatures = (features ?? []).filter((f) => { self.registerCallback(f) - const isShown: TagsFilter = layer.layerDef.isShown - const tags = f.properties - if (isShown !== undefined && !isShown.matchesProperties(tags)) { + if (!layer.isShown(f.properties, globalFilters)) { return false } - if (tags._deleted === "yes") { - return false - } - - let neededTags: TagsFilter = layer.currentFilter.data - if (neededTags !== undefined && !neededTags.matchesProperties(f.properties)) { - return false - } - - for (const globalFilter of globalFilters ?? []) { - const neededTags = globalFilter.osmTags - if (neededTags !== undefined && !neededTags.matchesProperties(f.properties)) { - return false - } - } includedFeatureIds.add(f.properties.id) return true diff --git a/Models/FilteredLayer.ts b/Models/FilteredLayer.ts index 9679f34470..e8ccd60a86 100644 --- a/Models/FilteredLayer.ts +++ b/Models/FilteredLayer.ts @@ -8,6 +8,7 @@ import { TagsFilter } from "../Logic/Tags/TagsFilter" import { Utils } from "../Utils" import { TagUtils } from "../Logic/Tags/TagUtils" import { And } from "../Logic/Tags/And" +import { GlobalFilter } from "./GlobalFilter" export default class FilteredLayer { /** @@ -62,7 +63,7 @@ export default class FilteredLayer { return JSON.stringify(values) } - public static stringToFieldProperties(value: string): Record { + private static stringToFieldProperties(value: string): Record { const values = JSON.parse(value) for (const key in values) { if (values[key] === "") { @@ -208,4 +209,34 @@ export default class FilteredLayer { } return optimized } + + /** + * Returns true if the given tags match the current filters (and the specified 'global filters') + */ + public isShown(properties: Record, globalFilters?: GlobalFilter[]): boolean { + if (properties._deleted === "yes") { + return false + } + { + const isShown: TagsFilter = this.layerDef.isShown + if (isShown !== undefined && !isShown.matchesProperties(properties)) { + return false + } + } + + { + let neededTags: TagsFilter = this.currentFilter.data + if (neededTags !== undefined && !neededTags.matchesProperties(properties)) { + return false + } + } + + for (const globalFilter of globalFilters ?? []) { + const neededTags = globalFilter.osmTags + if (neededTags !== undefined && !neededTags.matchesProperties(properties)) { + return false + } + } + return true + } } diff --git a/Models/MenuState.ts b/Models/MenuState.ts index aa737c2fd1..d613b00489 100644 --- a/Models/MenuState.ts +++ b/Models/MenuState.ts @@ -10,7 +10,7 @@ import { Utils } from "../Utils" * Some convenience methods are provided for this as well */ export class MenuState { - private static readonly _themeviewTabs = ["intro", "filters"] as const + private static readonly _themeviewTabs = ["intro", "filters", "download", "copyright"] as const public readonly themeIsOpened = new UIEventSource(true) public readonly themeViewTabIndex: UIEventSource public readonly themeViewTab: UIEventSource diff --git a/UI/BigComponents/DownloadPanel.ts b/UI/BigComponents/DownloadPanel.ts index a47703ba71..1527d1f962 100644 --- a/UI/BigComponents/DownloadPanel.ts +++ b/UI/BigComponents/DownloadPanel.ts @@ -7,24 +7,21 @@ import CheckBoxes from "../Input/Checkboxes" import { GeoOperations } from "../../Logic/GeoOperations" import Toggle from "../Input/Toggle" import Title from "../Base/Title" -import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" -import { UIEventSource } from "../../Logic/UIEventSource" +import { Store } from "../../Logic/UIEventSource" import SimpleMetaTagger from "../../Logic/SimpleMetaTagger" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { BBox } from "../../Logic/BBox" -import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" import geojson2svg from "geojson2svg" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import { SpecialVisualizationState } from "../SpecialVisualization" +import { Feature, FeatureCollection } from "geojson" +import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore" +import LayerState from "../../Logic/State/LayerState" export class DownloadPanel extends Toggle { - constructor(state: { - filteredLayers: UIEventSource - featurePipeline: FeaturePipeline - layoutToUse: LayoutConfig - currentBounds: UIEventSource - }) { + constructor(state: SpecialVisualizationState) { const t = Translations.t.general.download - const name = state.layoutToUse.id + const name = state.layout.id const includeMetaToggle = new CheckBoxes([t.includeMetaData]) const metaisIncluded = includeMetaToggle.GetValue().map((selected) => selected.length > 0) @@ -71,12 +68,13 @@ export class DownloadPanel extends Toggle { ) ).OnClickWithLoading(t.exporting, async () => { const geojson = DownloadPanel.getCleanGeoJsonPerLayer(state, metaisIncluded.data) - const leafletdiv = document.getElementById("leafletDiv") + const maindiv = document.getElementById("maindiv") + const layers = state.layout.layers.filter((l) => l.source !== null) const csv = DownloadPanel.asSvg(geojson, { - layers: state.filteredLayers.data.map((l) => l.layerDef), - mapExtent: state.currentBounds.data, - width: leafletdiv.offsetWidth, - height: leafletdiv.offsetHeight, + layers, + mapExtent: state.mapProperties.bounds.data, + width: maindiv.offsetWidth, + height: maindiv.offsetHeight, }) Utils.offerContentsAsDownloadableFile( @@ -97,7 +95,11 @@ export class DownloadPanel extends Toggle { t.licenseInfo.SetClass("link-underline"), ]).SetClass("w-full flex flex-col border-4 border-gray-300 rounded-3xl p-4") - super(downloadButtons, t.noDataLoaded, state.featurePipeline.somethingLoaded) + super( + downloadButtons, + t.noDataLoaded, + state.dataIsLoading.map((x) => !x) + ) } /** @@ -118,7 +120,7 @@ export class DownloadPanel extends Toggle { * DownloadPanel.asSvg(perLayer).replace(/\n/g, "") // => ` ` */ public static asSvg( - perLayer: Map, + perLayer: Map, options?: { layers?: LayerConfig[] width?: 1000 | number @@ -128,8 +130,11 @@ export class DownloadPanel extends Toggle { } ) { options = options ?? {} - const w = options.width ?? 1000 - const h = options.height ?? 1000 + const width = options.width ?? 1000 + const height = options.height ?? 1000 + if (width <= 0 || height <= 0) { + throw "Invalid width of height, they should be > 0" + } const unit = options.unit ?? "px" const mapExtent = { left: -180, bottom: -90, right: 180, top: 90 } if (options.mapExtent !== undefined) { @@ -139,7 +144,7 @@ export class DownloadPanel extends Toggle { mapExtent.bottom = bbox.minLat mapExtent.top = bbox.maxLat } - + console.log("Generateing svg, extent:", { mapExtent, width, height }) const elements: string[] = [] for (const layer of Array.from(perLayer.keys())) { @@ -152,7 +157,7 @@ export class DownloadPanel extends Toggle { const rendering = layerDef?.lineRendering[0] const converter = geojson2svg({ - viewportSize: { width: w, height: h }, + viewportSize: { width, height }, mapExtent, output: "svg", attributes: [ @@ -184,105 +189,85 @@ export class DownloadPanel extends Toggle { elements.push(group) } + const w = width + const h = height const header = `` return header + "\n" + elements.join("\n") + "\n" } - /** - * Gets all geojson as geojson feature - * @param state - * @param includeMetaData - * @private - */ private static getCleanGeoJson( state: { - featurePipeline: FeaturePipeline - currentBounds: UIEventSource - filteredLayers: UIEventSource + layout: LayoutConfig + mapProperties: { bounds: Store } + perLayer: ReadonlyMap + layerState: LayerState }, includeMetaData: boolean - ) { - const perLayer = DownloadPanel.getCleanGeoJsonPerLayer(state, includeMetaData) - const features = [].concat(...Array.from(perLayer.values())) + ): FeatureCollection { + const featuresPerLayer = DownloadPanel.getCleanGeoJsonPerLayer(state, includeMetaData) + const features = [].concat(...Array.from(featuresPerLayer.values())) return { type: "FeatureCollection", features, } } - private static getCleanGeoJsonPerLayer( - state: { - featurePipeline: FeaturePipeline - currentBounds: UIEventSource - filteredLayers: UIEventSource - }, - includeMetaData: boolean - ): Map /*{layerId --> geojsonFeatures[]}*/ { - const perLayer = new Map() - const neededLayers = state.filteredLayers.data.map((l) => l.layerDef.id) - const bbox = state.currentBounds.data - const featureList = state.featurePipeline.GetAllFeaturesAndMetaWithin( - bbox, - new Set(neededLayers) - ) - for (const tile of featureList) { - if (tile.layer !== undefined) { - continue - } - - const layer = state.filteredLayers.data.find((fl) => fl.layerDef.id === tile.layer) - if (!perLayer.has(tile.layer)) { - perLayer.set(tile.layer, []) - } - const featureList = perLayer.get(tile.layer) - const filters = layer.appliedFilters.data - perfeature: for (const feature of tile.features) { - if (!bbox.overlapsWith(BBox.get(feature))) { - continue - } - - if (filters !== undefined) { - for (let key of Array.from(filters.keys())) { - const filter: FilterState = filters.get(key) - if (filter?.currentFilter === undefined) { - continue - } - if (!filter.currentFilter.matchesProperties(feature.properties)) { - continue perfeature - } - } - } - - const cleaned = { - type: feature.type, - geometry: { ...feature.geometry }, - properties: { ...feature.properties }, - } - - if (!includeMetaData) { - for (const key in cleaned.properties) { - if (key === "_lon" || key === "_lat") { - continue - } - if (key.startsWith("_")) { - delete feature.properties[key] - } - } - } - - const datedKeys = [].concat( - SimpleMetaTagger.metatags - .filter((tagging) => tagging.includesDates) - .map((tagging) => tagging.keys) - ) - for (const key of datedKeys) { - delete feature.properties[key] - } - - featureList.push(cleaned) - } + /** + * Returns a new feature of which all the metatags are deleted + */ + private static cleanFeature(f: Feature): Feature { + f = { + type: f.type, + geometry: { ...f.geometry }, + properties: { ...f.properties }, } - return perLayer + for (const key in f.properties) { + if (key === "_lon" || key === "_lat") { + continue + } + if (key.startsWith("_")) { + delete f.properties[key] + } + } + const datedKeys = [].concat( + SimpleMetaTagger.metatags + .filter((tagging) => tagging.includesDates) + .map((tagging) => tagging.keys) + ) + for (const key of datedKeys) { + delete f.properties[key] + } + return f + } + + private static getCleanGeoJsonPerLayer( + state: { + layout: LayoutConfig + mapProperties: { bounds: Store } + perLayer: ReadonlyMap + layerState: LayerState + }, + includeMetaData: boolean + ): Map { + const featuresPerLayer = new Map() + const neededLayers = state.layout.layers.filter((l) => l.source !== null).map((l) => l.id) + const bbox = state.mapProperties.bounds.data + + for (const neededLayer of neededLayers) { + const indexedFeatureSource = state.perLayer.get(neededLayer) + let features = indexedFeatureSource.GetFeaturesWithin(bbox, true) + // The 'indexedFeatureSources' contains _all_ features, they are not filtered yet + const filter = state.layerState.filteredLayers.get(neededLayer) + features = features.filter((f) => + filter.isShown(f.properties, state.layerState.globalFilters.data) + ) + if (!includeMetaData) { + features = features.map((f) => DownloadPanel.cleanFeature(f)) + } + featuresPerLayer.set(neededLayer, features) + } + + return featuresPerLayer } } diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 6d2d4c50f5..9b66947ae0 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -28,7 +28,8 @@ import UserRelatedState from "../Logic/State/UserRelatedState"; import LoginToggle from "./Base/LoginToggle.svelte"; import LoginButton from "./Base/LoginButton.svelte"; - import CopyrightPanel from "./BigComponents/CopyrightPanel.js"; + import CopyrightPanel from "./BigComponents/CopyrightPanel"; + import { DownloadPanel } from "./BigComponents/DownloadPanel"; export let state: ThemeViewState; let layout = state.layout; @@ -150,12 +151,21 @@
+
+ + +
+
+ new DownloadPanel(state)}/> +
-
+
- new CopyrightPanel(state)}> + new CopyrightPanel(state)}> + + diff --git a/langs/en.json b/langs/en.json index 43cf285b3f..3d0f1f6732 100644 --- a/langs/en.json +++ b/langs/en.json @@ -173,7 +173,7 @@ "includeMetaData": "Include metadata (last editor, calculated values, …)", "licenseInfo": "

Copyright notice

The provided data is available under ODbL. Reusing it is gratis for any purpose, but
  • the attribution © OpenStreetMap contributors is required
  • Any change must be use the license
Please read the full copyright notice for details.", "noDataLoaded": "No data is loaded yet. Download will be available soon", - "title": "Download visible data", + "title": "Download", "uploadGpx": "Upload your track to OpenStreetMap" }, "error": "Something went wrong", diff --git a/package-lock.json b/package-lock.json index 532d683426..6eff6d6d43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "email-validator": "^2.0.4", "escape-html": "^1.0.3", "fake-dom": "^1.0.4", - "geojson2svg": "^1.3.1", + "geojson2svg": "^1.3.3", "html-to-markdown": "^1.0.0", "i18next-client": "^1.11.4", "idb-keyval": "^6.0.3", diff --git a/package.json b/package.json index 771d998b68..ea5c8a9c16 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "email-validator": "^2.0.4", "escape-html": "^1.0.3", "fake-dom": "^1.0.4", - "geojson2svg": "^1.3.1", + "geojson2svg": "^1.3.3", "html-to-markdown": "^1.0.0", "i18next-client": "^1.11.4", "idb-keyval": "^6.0.3", diff --git a/theme.html b/theme.html index 8ef3c47a16..1623a00c92 100644 --- a/theme.html +++ b/theme.html @@ -41,7 +41,7 @@ -
+
Loading MapComplete, hang on... From adab786375d694f5c5b93be0a9d051bf526220d5 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 14 Apr 2023 04:34:13 +0200 Subject: [PATCH 033/257] refactoring: Remove border --- UI/BigComponents/DownloadPanel.ts | 2 +- UI/Popup/FeatureInfoBox.ts | 94 +------------------------------ 2 files changed, 2 insertions(+), 94 deletions(-) diff --git a/UI/BigComponents/DownloadPanel.ts b/UI/BigComponents/DownloadPanel.ts index 1527d1f962..d46e0ab03f 100644 --- a/UI/BigComponents/DownloadPanel.ts +++ b/UI/BigComponents/DownloadPanel.ts @@ -93,7 +93,7 @@ export class DownloadPanel extends Toggle { buttonSvg, includeMetaToggle, t.licenseInfo.SetClass("link-underline"), - ]).SetClass("w-full flex flex-col border-4 border-gray-300 rounded-3xl p-4") + ]).SetClass("w-full flex flex-col") super( downloadButtons, diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index 3efe5d9ede..9a56c1cecb 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -1,5 +1,4 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" -import EditableTagRendering from "./EditableTagRendering" import QuestionBox from "./QuestionBox" import Combine from "../Base/Combine" import TagRenderingAnswer from "./TagRenderingAnswer" @@ -8,12 +7,7 @@ import Constants from "../../Models/Constants" import SharedTagRenderings from "../../Customizations/SharedTagRenderings" import BaseUIElement from "../BaseUIElement" import { VariableUiElement } from "../Base/VariableUIElement" -import DeleteWizard from "./DeleteWizard" -import SplitRoadWizard from "./SplitRoadWizard" -import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { Utils } from "../../Utils" -import MoveWizard from "./MoveWizard" import Toggle from "../Input/Toggle" import Lazy from "../Base/Lazy" import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" @@ -36,7 +30,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { [state.showAllQuestionsAtOnce] ) super( - () => FeatureInfoBox.GenerateTitleBar(tags, layerConfig, state), + () => undefined, () => FeatureInfoBox.GenerateContent(tags, layerConfig, state, showAllQuestions), options?.hashToShow ?? tags.data.id ?? "item", options?.isShown, @@ -48,34 +42,6 @@ export default class FeatureInfoBox extends ScrollableFullScreen { } } - public static GenerateTitleBar( - tags: UIEventSource, - layerConfig: LayerConfig, - state: {} - ): BaseUIElement { - const title = new TagRenderingAnswer( - tags, - layerConfig.title ?? new TagRenderingConfig("POI"), - state - ).SetClass("break-words font-bold sm:p-0.5 md:p-1 sm:p-1.5 md:p-2 text-2xl") - const titleIcons = new Combine( - layerConfig.titleIcons.map((icon) => { - return new TagRenderingAnswer( - tags, - icon, - state, - "block h-8 max-h-8 align-baseline box-content sm:p-0.5 titleicon" - ) - }) - ).SetClass("flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2") - - return new Combine([ - new Combine([title, titleIcons]).SetClass( - "flex flex-col sm:flex-row flex-grow justify-between" - ), - ]) - } - public static GenerateContent( tags: UIEventSource, layerConfig: LayerConfig, @@ -98,7 +64,6 @@ export default class FeatureInfoBox extends ScrollableFullScreen { ): BaseUIElement { let questionBoxes: Map = new Map() const t = Translations.t.general - const allGroupNames = Utils.Dedup(layerConfig.tagRenderings.map((tr) => tr.group)) const withQuestion = layerConfig.tagRenderings.filter( (tr) => tr.question !== undefined @@ -134,63 +99,6 @@ export default class FeatureInfoBox extends ScrollableFullScreen { }) ), ] - for (let i = 0; i < allGroupNames.length; i++) { - const groupName = allGroupNames[i] - - const trs = layerConfig.tagRenderings.filter((tr) => tr.group === groupName) - const renderingsForGroup: (EditableTagRendering | BaseUIElement)[] = [] - const innerClasses = - "block w-full break-word text-default m-1 p-1 border-b border-gray-200 mb-2 pb-2" - for (const tr of trs) { - if (tr.question === null || tr.id === "questions") { - // This is a question box! - const questionBox = questionBoxes.get(tr.group) - questionBoxes.delete(tr.group) - - if (tr.render !== undefined) { - questionBox.SetClass("text-sm") - const renderedQuestion = new TagRenderingAnswer( - tags, - tr, - state, - tr.group + " questions", - "", - { - specialViz: new Map([ - ["questions", questionBox], - ]), - } - ) - const possiblyHidden = new Toggle( - renderedQuestion, - undefined, - questionBox.restingQuestions.map((ls) => ls?.length > 0) - ) - renderingsForGroup.push(possiblyHidden) - } else { - renderingsForGroup.push(questionBox) - } - } else { - let classes = innerClasses - let isHeader = renderingsForGroup.length === 0 && i > 0 - if (isHeader) { - // This is the first element of a group! - // It should act as header and be sticky - classes = "" - } - - const etr = new EditableTagRendering(tags, tr, layerConfig.units, state, { - innerElementClasses: innerClasses, - }) - if (isHeader) { - etr.SetClass("sticky top-0") - } - renderingsForGroup.push(etr) - } - } - - allRenderings.push(...renderingsForGroup) - } allRenderings.push( new Toggle( new Lazy(() => From 3db87232f288b878ca6c335b76f635aabc8a4759 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 14 Apr 2023 17:53:08 +0200 Subject: [PATCH 034/257] Refactoring: add metatagging --- Logic/ExtraFunctions.ts | 56 ++++---- .../Actors/MetaTagRecalculator.ts | 124 ------------------ Logic/MetaTagging.ts | 30 +++++ Logic/Osm/OsmConnection.ts | 2 +- Logic/SimpleMetaTagger.ts | 7 +- Logic/State/LayerState.ts | 3 +- Logic/State/UserRelatedState.ts | 24 +++- Models/FilteredLayer.ts | 2 +- .../Json/TagRenderingConfigJson.ts | 7 + Models/ThemeViewState.ts | 11 +- test.ts | 26 +--- 11 files changed, 101 insertions(+), 191 deletions(-) delete mode 100644 Logic/FeatureSource/Actors/MetaTagRecalculator.ts diff --git a/Logic/ExtraFunctions.ts b/Logic/ExtraFunctions.ts index f0a7d795b0..80311e3750 100644 --- a/Logic/ExtraFunctions.ts +++ b/Logic/ExtraFunctions.ts @@ -12,8 +12,8 @@ export interface ExtraFuncParams { * Note that more features then requested can be given back. * Format: [ [ geojson, geojson, geojson, ... ], [geojson, ...], ...] */ - getFeaturesWithin: (layerId: string, bbox: BBox) => Feature[][] - getFeatureById: (id: string) => Feature + getFeaturesWithin: (layerId: string, bbox: BBox) => Feature>[] + getFeatureById: (id: string) => Feature> } /** @@ -52,26 +52,24 @@ class EnclosingFunc implements ExtraFunction { if (otherFeaturess.length === 0) { continue } - for (const otherFeatures of otherFeaturess) { - for (const otherFeature of otherFeatures) { - if (seenIds.has(otherFeature.properties.id)) { - continue - } - seenIds.add(otherFeature.properties.id) - if ( - otherFeature.geometry.type !== "Polygon" && - otherFeature.geometry.type !== "MultiPolygon" - ) { - continue - } - if ( - GeoOperations.completelyWithin( - feat, - >otherFeature - ) - ) { - result.push({ feat: otherFeature }) - } + for (const otherFeature of otherFeaturess) { + if (seenIds.has(otherFeature.properties.id)) { + continue + } + seenIds.add(otherFeature.properties.id) + if ( + otherFeature.geometry.type !== "Polygon" && + otherFeature.geometry.type !== "MultiPolygon" + ) { + continue + } + if ( + GeoOperations.completelyWithin( + feat, + >otherFeature + ) + ) { + result.push({ feat: otherFeature }) } } } @@ -154,14 +152,12 @@ class IntersectionFunc implements ExtraFunction { if (otherLayers.length === 0) { continue } - for (const tile of otherLayers) { - for (const otherFeature of tile) { - const intersections = GeoOperations.LineIntersections(feat, otherFeature) - if (intersections.length === 0) { - continue - } - result.push({ feat: otherFeature, intersections }) + for (const otherFeature of otherLayers) { + const intersections = GeoOperations.LineIntersections(feat, otherFeature) + if (intersections.length === 0) { + continue } + result.push({ feat: otherFeature, intersections }) } } @@ -329,7 +325,7 @@ class ClosestNObjectFunc implements ExtraFunction { const uniqueTagsMatch = otherFeature.properties[uniqueTag] !== undefined && closestFeature.feat.properties[uniqueTag] === - otherFeature.properties[uniqueTag] + otherFeature.properties[uniqueTag] if (uniqueTagsMatch) { targetIndex = -1 if (closestFeature.distance > distance) { diff --git a/Logic/FeatureSource/Actors/MetaTagRecalculator.ts b/Logic/FeatureSource/Actors/MetaTagRecalculator.ts deleted file mode 100644 index 0cda95578d..0000000000 --- a/Logic/FeatureSource/Actors/MetaTagRecalculator.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { FeatureSourceForLayer, Tiled } from "../FeatureSource" -import MetaTagging from "../../MetaTagging" -import { ExtraFuncParams } from "../../ExtraFunctions" -import { BBox } from "../../BBox" -import { UIEventSource } from "../../UIEventSource" - -/** - * Concerned with the logic of updating the right layer at the right time - */ -class MetatagUpdater { - public readonly neededLayerBboxes = new Map() - private source: FeatureSourceForLayer & Tiled - private readonly params: ExtraFuncParams - private state: { allElements?: ElementStorage } - - private readonly isDirty = new UIEventSource(false) - - constructor( - source: FeatureSourceForLayer & Tiled, - state: { allElements?: ElementStorage }, - featurePipeline: FeaturePipeline - ) { - this.state = state - this.source = source - const self = this - this.params = { - getFeatureById(id) { - return state.allElements.ContainingFeatures.get(id) - }, - getFeaturesWithin(layerId, bbox) { - // We keep track of the BBOX that this source needs - let oldBbox: BBox = self.neededLayerBboxes.get(layerId) - if (oldBbox === undefined) { - self.neededLayerBboxes.set(layerId, bbox) - } else if (!bbox.isContainedIn(oldBbox)) { - self.neededLayerBboxes.set(layerId, oldBbox.unionWith(bbox)) - } - return featurePipeline.GetFeaturesWithin(layerId, bbox) - }, - } - this.isDirty.stabilized(100).addCallback((dirty) => { - if (dirty) { - self.updateMetaTags() - } - }) - this.source.features.addCallbackAndRunD((_) => self.isDirty.setData(true)) - } - - public requestUpdate() { - this.isDirty.setData(true) - } - - public updateMetaTags() { - const features = this.source.features.data - - if (features.length === 0) { - this.isDirty.setData(false) - return - } - MetaTagging.addMetatags(features, this.params, this.source.layer.layerDef, this.state) - this.isDirty.setData(false) - } -} - -export default class MetaTagRecalculator { - private _state: { - allElements?: ElementStorage - } - private _featurePipeline: FeaturePipeline - private readonly _alreadyRegistered: Set = new Set< - FeatureSourceForLayer & Tiled - >() - private readonly _notifiers: MetatagUpdater[] = [] - - /** - * The meta tag recalculator receives tiles of layers via the 'registerSource'-function. - * It keeps track of which sources have had their share calculated, and which should be re-updated if some other data is loaded - */ - constructor( - state: { allElements?: ElementStorage; currentView: FeatureSourceForLayer & Tiled }, - featurePipeline: FeaturePipeline - ) { - this._featurePipeline = featurePipeline - this._state = state - - if (state.currentView !== undefined) { - const currentViewUpdater = new MetatagUpdater( - state.currentView, - this._state, - this._featurePipeline - ) - this._alreadyRegistered.add(state.currentView) - this._notifiers.push(currentViewUpdater) - state.currentView.features.addCallback((_) => { - console.debug("Requesting an update for currentView") - currentViewUpdater.updateMetaTags() - }) - } - } - - public registerSource(source: FeatureSourceForLayer & Tiled) { - if (source === undefined) { - return - } - if (this._alreadyRegistered.has(source)) { - return - } - this._alreadyRegistered.add(source) - this._notifiers.push(new MetatagUpdater(source, this._state, this._featurePipeline)) - const self = this - source.features.addCallbackAndRunD((_) => { - const layerName = source.layer.layerDef.id - for (const updater of self._notifiers) { - const neededBbox = updater.neededLayerBboxes.get(layerName) - if (neededBbox == undefined) { - continue - } - if (source.bbox === undefined || neededBbox.overlapsWith(source.bbox)) { - updater.requestUpdate() - } - } - }) - } -} diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index ae59c95c08..2344b32efc 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -4,6 +4,8 @@ import LayerConfig from "../Models/ThemeConfig/LayerConfig" import { Feature } from "geojson" import FeaturePropertiesStore from "./FeatureSource/Actors/FeaturePropertiesStore" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" +import { GeoIndexedStoreForLayer } from "./FeatureSource/Actors/GeoIndexedStore" +import { IndexedFeatureSource } from "./FeatureSource/FeatureSource" /** * Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ... @@ -15,6 +17,34 @@ export default class MetaTagging { private static readonly stopErrorOutputAt = 10 private static retaggingFuncCache = new Map void)[]>() + constructor(state: { + layout: LayoutConfig + perLayer: ReadonlyMap + indexedFeatures: IndexedFeatureSource + featureProperties: FeaturePropertiesStore + }) { + const params: ExtraFuncParams = { + getFeatureById: (id) => state.indexedFeatures.featuresById.data.get(id), + getFeaturesWithin: (layerId, bbox) => + state.perLayer.get(layerId).GetFeaturesWithin(bbox), + } + for (const layer of state.layout.layers) { + if (layer.source === null) { + continue + } + const featureSource = state.perLayer.get(layer.id) + featureSource.features?.addCallbackAndRunD((features) => { + MetaTagging.addMetatags( + features, + params, + layer, + state.layout, + state.featureProperties + ) + }) + } + } + /** * This method (re)calculates all metatags and calculated tags on every given object. * The given features should be part of the given layer diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 7145a0334c..745daa6454 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -237,7 +237,7 @@ export class OsmConnection { userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 ) data.tracesCount = Number.parseInt( - userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? 0 + userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? 0 ) data.img = undefined diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index ae8f9c8df0..1fe54e32a7 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -95,6 +95,7 @@ export class ReferencingWaysMetaTagger extends SimpleMetaTagger { if (!id.startsWith("node/")) { return false } + console.trace("Downloading referencing ways for", feature.properties.id) OsmObject.DownloadReferencingWays(id).then((referencingWays) => { const currentTagsSource = state.allElements?.getEventSourceById(id) ?? [] @@ -111,7 +112,7 @@ export class ReferencingWaysMetaTagger extends SimpleMetaTagger { } } -export class CountryTagger extends SimpleMetaTagger { +class CountryTagger extends SimpleMetaTagger { private static readonly coder = new CountryCoder( Constants.countryCoderEndpoint, Utils.downloadJson @@ -182,7 +183,7 @@ class InlineMetaTagger extends SimpleMetaTagger { } } -export class RewriteMetaInfoTags extends SimpleMetaTagger { +class RewriteMetaInfoTags extends SimpleMetaTagger { constructor() { super({ keys: [ @@ -267,8 +268,6 @@ export default class SimpleMetaTaggers { const lon = centerPoint.geometry.coordinates[0] feature.properties["_lat"] = "" + lat feature.properties["_lon"] = "" + lon - feature._lon = lon // This is dirty, I know - feature._lat = lat return true } ) diff --git a/Logic/State/LayerState.ts b/Logic/State/LayerState.ts index 268cdfaf7d..fc1f67135a 100644 --- a/Logic/State/LayerState.ts +++ b/Logic/State/LayerState.ts @@ -69,6 +69,7 @@ export default class LayerState { console.warn( "Linking filter and isDisplayed-states of " + layer.id + " and " + layer.filterIsSameAs ) - filteredLayers.set(layer.id, toReuse) + const copy = new FilteredLayer(layer, toReuse.appliedFilters, toReuse.isDisplayed) + filteredLayers.set(layer.id, copy) } } diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index 21399d8a06..a6879f4f80 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -13,6 +13,7 @@ import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" import usersettings from "../../assets/generated/layers/usersettings.json" import Locale from "../../UI/i18n/Locale" import LinkToWeblate from "../../UI/Base/LinkToWeblate" +import FeatureSwitchState from "./FeatureSwitchState" /** * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, @@ -57,7 +58,8 @@ export default class UserRelatedState { constructor( osmConnection: OsmConnection, availableLanguages?: string[], - layout?: LayoutConfig + layout?: LayoutConfig, + featureSwitches?: FeatureSwitchState ) { this.osmConnection = osmConnection { @@ -97,7 +99,7 @@ export default class UserRelatedState { this.homeLocation = this.initHomeLocation() - this.preferencesAsTags = this.initAmendedPrefs(layout) + this.preferencesAsTags = this.initAmendedPrefs(layout, featureSwitches) } public GetUnofficialTheme(id: string): @@ -241,7 +243,10 @@ export default class UserRelatedState { * Initialize the 'amended preferences'. * This is inherently a dirty and chaotic method, as it shoves many properties into this EventSourcd * */ - private initAmendedPrefs(layout?: LayoutConfig): UIEventSource> { + private initAmendedPrefs( + layout?: LayoutConfig, + featureSwitches?: FeatureSwitchState + ): UIEventSource> { const amendedPrefs = new UIEventSource>({ _theme: layout?.id, _backend: this.osmConnection.Backend(), @@ -357,6 +362,19 @@ export default class UserRelatedState { } }) + for (const key in featureSwitches) { + if (featureSwitches[key].addCallbackAndRun) { + featureSwitches[key].addCallbackAndRun((v) => { + const oldV = amendedPrefs.data["__" + key] + if (oldV === v) { + return + } + amendedPrefs.data["__" + key] = "" + v + amendedPrefs.ping() + }) + } + } + return amendedPrefs } } diff --git a/Models/FilteredLayer.ts b/Models/FilteredLayer.ts index e8ccd60a86..9e4d83b310 100644 --- a/Models/FilteredLayer.ts +++ b/Models/FilteredLayer.ts @@ -35,7 +35,7 @@ export default class FilteredLayer { constructor( layer: LayerConfig, - appliedFilters?: Map>, + appliedFilters?: ReadonlyMap>, isDisplayed?: UIEventSource ) { this.layerDef = layer diff --git a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts index 80d3209f6f..d42de17d09 100644 --- a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts @@ -76,6 +76,13 @@ export interface TagRenderingConfigJson { * */ condition?: TagConfigJson + /** + * If set, this tag will be evaluated agains the _usersettings/application state_ table. + * Enable 'show debug info' in user settings to see available options. + * Note that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_ + */ + metacondition?: TagConfigJson + /** * Allow freeform text input from the user */ diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index b63508ed46..16217e6d30 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -41,6 +41,7 @@ import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexe import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource" import SimpleFeatureSource from "../Logic/FeatureSource/Sources/SimpleFeatureSource" import { MenuState } from "./MenuState" +import MetaTagging from "../Logic/MetaTagging" /** * @@ -101,7 +102,12 @@ export default class ThemeViewState implements SpecialVisualizationState { ), osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data, }) - this.userRelatedState = new UserRelatedState(this.osmConnection, layout?.language, layout) + this.userRelatedState = new UserRelatedState( + this.osmConnection, + layout?.language, + layout, + this.featureSwitches + ) this.selectedElement = new UIEventSource(undefined, "Selected element") this.selectedLayer = new UIEventSource(undefined, "Selected layer") this.geolocation = new GeoLocationHandler( @@ -323,10 +329,9 @@ export default class ThemeViewState implements SpecialVisualizationState { /** * Setup various services for which no reference are needed - * @private */ private initActors() { - // Various actors that we don't need to reference + new MetaTagging(this) new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) new ChangeToElementsActor(this.changes, this.featureProperties) new PendingChangesUploader(this.changes, this.selectedElement) diff --git a/test.ts b/test.ts index 59243ea891..bed47cf8bb 100644 --- a/test.ts +++ b/test.ts @@ -1,24 +1,10 @@ -import SvelteUIElement from "./UI/Base/SvelteUIElement" -import ThemeViewGUI from "./UI/ThemeViewGUI.svelte" -import { FixedUiElement } from "./UI/Base/FixedUiElement" import LayoutConfig from "./Models/ThemeConfig/LayoutConfig" import * as theme from "./assets/generated/themes/shops.json" import ThemeViewState from "./Models/ThemeViewState" import Combine from "./UI/Base/Combine" import SpecialVisualizations from "./UI/SpecialVisualizations" -import AddNewPoint from "./UI/Popup/AddNewPoint/AddNewPoint.svelte" -import UserProfile from "./UI/BigComponents/UserProfile.svelte" -async function main() { - new FixedUiElement("").AttachTo("extradiv") - const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) - const state = new ThemeViewState(layout) - - const main = new SvelteUIElement(ThemeViewGUI, { state }) - main.AttachTo("maindiv") -} - -async function testspecial() { +function testspecial() { const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) const state = new ThemeViewState(layout) @@ -28,12 +14,4 @@ async function testspecial() { new Combine(all).AttachTo("maindiv") } -async function test() { - const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) - const state = new ThemeViewState(layout) - new SvelteUIElement(UserProfile, { osmConnection: state.osmConnection }).AttachTo("maindiv") -} - -/* -test().then((_) => {}) /*/ -main().then((_) => {}) //*/ +testspecial() From 105120060d219fcc5f78d5ee422837a7c2309b09 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 15 Apr 2023 02:28:24 +0200 Subject: [PATCH 035/257] Refactoring: add metatagging, add 'last edited by' element, add 'metacondition' --- Logic/DetermineLayout.ts | 6 +- .../Actors/FeaturePropertiesStore.ts | 6 +- Logic/MetaTagging.ts | 3 +- Logic/SimpleMetaTagger.ts | 12 ++-- Models/ThemeConfig/Conversion/PrepareLayer.ts | 30 ++++++++- Models/ThemeConfig/TagRenderingConfig.ts | 18 +++--- Models/ThemeViewState.ts | 6 ++ UI/BigComponents/SelectedElementView.svelte | 7 ++- UI/Map/MapLibreAdaptor.ts | 1 + UI/Popup/AddNewPoint/AddNewPoint.svelte | 6 +- UI/Popup/AllTagsPanel.svelte | 61 ++++++++++++------- UI/Popup/FeatureInfoBox.ts | 56 ----------------- UI/Popup/TagRendering/Questionbox.svelte | 6 +- .../TagRendering/TagRenderingQuestion.svelte | 1 - UI/SpecialVisualization.ts | 1 + UI/SpecialVisualizations.ts | 19 +++--- UI/i18n/Translation.ts | 9 ++- Utils.ts | 11 +++- assets/layers/bench/license_info.json | 2 +- assets/layers/last_click/last_click.json | 11 +++- assets/tagRenderings/questions.json | 20 +++++- langs/layers/ca.json | 5 +- langs/layers/de.json | 6 +- langs/layers/en.json | 9 +-- langs/layers/es.json | 2 +- langs/layers/it.json | 6 +- langs/layers/nl.json | 10 +-- langs/shared-questions/en.json | 7 +++ langs/shared-questions/nl.json | 7 +++ scripts/generateLayerOverview.ts | 10 ++- .../OSM/Actions/ReplaceGeometryAction.spec.ts | 5 +- 31 files changed, 217 insertions(+), 142 deletions(-) diff --git a/Logic/DetermineLayout.ts b/Logic/DetermineLayout.ts index 020467758d..d2dc46175c 100644 --- a/Logic/DetermineLayout.ts +++ b/Logic/DetermineLayout.ts @@ -62,7 +62,11 @@ export default class DetermineLayout { layoutId, "The layout to load into MapComplete" ).data - return AllKnownLayouts.allKnownLayouts.get(layoutId?.toLowerCase()) + const layout = AllKnownLayouts.allKnownLayouts.get(layoutId?.toLowerCase()) + if (layout === undefined) { + throw "No layout with name " + layoutId + " exists" + } + return layout } public static LoadLayoutFromHash(userLayoutParam: UIEventSource): LayoutConfig | null { diff --git a/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts b/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts index b78cc79d6e..1708852d97 100644 --- a/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts +++ b/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts @@ -6,7 +6,7 @@ import { UIEventSource } from "../../UIEventSource" */ export default class FeaturePropertiesStore { private readonly _source: FeatureSource & IndexedFeatureSource - private readonly _elements = new Map>() + private readonly _elements = new Map>>() constructor(source: FeatureSource & IndexedFeatureSource) { this._source = source @@ -83,7 +83,9 @@ export default class FeaturePropertiesStore { return changeMade } - addAlias(oldId: string, newId: string): void { + // noinspection JSUnusedGlobalSymbols + public addAlias(oldId: string, newId: string): void { + console.log("FeaturePropertiesStore: adding alias for", oldId, newId) if (newId === undefined) { // We removed the node/way/relation with type 'type' and id 'oldId' on openstreetmap! const element = this._elements.get(oldId) diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 2344b32efc..ce1d143fa5 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -94,8 +94,9 @@ export default class MetaTagging { let definedTags = new Set(Object.getOwnPropertyNames(feature.properties)) for (const metatag of metatagsToApply) { try { - if (!metatag.keys.some((key) => feature.properties[key] === undefined)) { + if (!metatag.keys.some((key) => !(key in feature.properties))) { // All keys are already defined, we probably already ran this one + // Note that we use 'key in properties', not 'properties[key] === undefined'. The latter will cause evaluation of lazy properties continue } diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index 1fe54e32a7..d1e28704ce 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -96,16 +96,11 @@ export class ReferencingWaysMetaTagger extends SimpleMetaTagger { return false } - console.trace("Downloading referencing ways for", feature.properties.id) - OsmObject.DownloadReferencingWays(id).then((referencingWays) => { - const currentTagsSource = state.allElements?.getEventSourceById(id) ?? [] + Utils.AddLazyPropertyAsync(feature.properties, "_referencing_ways", async () => { + const referencingWays = await OsmObject.DownloadReferencingWays(id) const wayIds = referencingWays.map((w) => "way/" + w.id) wayIds.sort() - const wayIdsStr = wayIds.join(";") - if (wayIdsStr !== "" && currentTagsSource.data["_referencing_ways"] !== wayIdsStr) { - currentTagsSource.data["_referencing_ways"] = wayIdsStr - currentTagsSource.ping() - } + return wayIds.join(";") }) return true @@ -221,6 +216,7 @@ class RewriteMetaInfoTags extends SimpleMetaTagger { return movedSomething } } + export default class SimpleMetaTaggers { /** * A simple metatagger which rewrites various metatags as needed diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index 680dc166d5..370464d19c 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -575,12 +575,14 @@ export class AddQuestionBox extends DesugaringStep { } export class AddEditingElements extends DesugaringStep { - constructor() { + private readonly _desugaring: DesugaringContext + constructor(desugaring: DesugaringContext) { super( "Add some editing elements, such as the delete button or the move button if they are configured. These used to be handled by the feature info box, but this has been replaced by special visualisation elements", [], "AddEditingElements" ) + this._desugaring = desugaring } convert( @@ -609,6 +611,30 @@ export class AddEditingElements extends DesugaringStep { }) } + if (json.deletion && !ValidationUtils.hasSpecialVisualisation(json, "all_tags")) { + const trc: TagRenderingConfigJson = { + id: "all-tags", + render: { "*": "{all_tags()}" }, + metacondition: { + or: [ + "__featureSwitchIsTesting=true", + "__featureSwitchIsDebugging=true", + "mapcomplete-show_debug=yes", + ], + }, + } + json.tagRenderings.push(trc) + } + + if ( + json.source !== "special" && + json.source !== "special:library" && + json.tagRenderings && + !json.tagRenderings.some((tr) => tr["id"] === "last_edit") + ) { + json.tagRenderings.push(this._desugaring.tagRenderings.get("last_edit")) + } + return { result: json } } } @@ -1145,7 +1171,7 @@ export class PrepareLayer extends Fuse { new On("tagRenderings", new Each(new DetectInline())), new AddQuestionBox(), new AddMiniMap(state), - new AddEditingElements(), + new AddEditingElements(state), new On("mapRendering", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), new On<(PointRenderingConfigJson | LineRenderingConfigJson)[], LayerConfigJson>( "mapRendering", diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index 7105d021f2..1c68e0eaad 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -47,6 +47,10 @@ export default class TagRenderingConfig { public readonly question?: TypedTranslation public readonly questionhint?: TypedTranslation public readonly condition?: TagsFilter + /** + * Evaluated against the current 'usersettings'-state + */ + public readonly metacondition?: TagsFilter public readonly description?: Translation public readonly configuration_warnings: string[] = [] @@ -70,14 +74,6 @@ export default class TagRenderingConfig { if (json === undefined) { throw "Initing a TagRenderingConfig with undefined in " + context } - if (json === "questions") { - // Very special value - this.render = null - this.question = null - this.condition = null - this.id = "questions" - return - } if (typeof json === "number") { json = "" + json @@ -114,11 +110,15 @@ export default class TagRenderingConfig { } this.labels = json.labels ?? [] - this.render = Translations.T(json.render, translationKey + ".render") + this.render = Translations.T(json.render, translationKey + ".render") this.question = Translations.T(json.question, translationKey + ".question") this.questionhint = Translations.T(json.questionHint, translationKey + ".questionHint") this.description = Translations.T(json.description, translationKey + ".description") this.condition = TagUtils.Tag(json.condition ?? { and: [] }, `${context}.condition`) + this.metacondition = TagUtils.Tag( + json.metacondition ?? { and: [] }, + `${context}.metacondition` + ) if (json.freeform) { if ( json.freeform.addExtraTags !== undefined && diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 16217e6d30..82323baf57 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -205,6 +205,12 @@ export default class ThemeViewState implements SpecialVisualizationState { */ private miscSetup() { this.userRelatedState.markLayoutAsVisited(this.layout) + + this.selectedElement.addCallbackAndRunD(() => { + // As soon as we have a selected element, we clear it + // This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature + this.lastClickObject.features.setData([]) + }) } private initHotkeys() { diff --git a/UI/BigComponents/SelectedElementView.svelte b/UI/BigComponents/SelectedElementView.svelte index 493af1d355..5899e05762 100644 --- a/UI/BigComponents/SelectedElementView.svelte +++ b/UI/BigComponents/SelectedElementView.svelte @@ -18,6 +18,11 @@ onDestroy(tags.addCallbackAndRun(tags => { _tags = tags; })); + + let _metatags: Record + onDestroy(state.userRelatedState.preferencesAsTags .addCallbackAndRun(tags => { + _metatags = tags; + }));
@@ -40,7 +45,7 @@
{#each layer.tagRenderings as config (config.id)} - {#if config.condition === undefined || config.condition.matchesProperties(_tags)} + {#if (config.condition === undefined || config.condition.matchesProperties(_tags)) && (config.metacondition === undefined || config.metacondition.matchesProperties(_metatags))} {#if config.IsKnown(_tags)} {/if} diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index e4e0d03d7d..b05d3c0bef 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -91,6 +91,7 @@ export class MapLibreAdaptor implements MapProperties { // Workaround, 'ShowPointLayer' sets this flag return } + console.log(e) const lon = e.lngLat.lng const lat = e.lngLat.lat lastClickLocation.setData({ lon, lat }) diff --git a/UI/Popup/AddNewPoint/AddNewPoint.svelte b/UI/Popup/AddNewPoint/AddNewPoint.svelte index 75b35a0d73..26b3975918 100644 --- a/UI/Popup/AddNewPoint/AddNewPoint.svelte +++ b/UI/Popup/AddNewPoint/AddNewPoint.svelte @@ -96,14 +96,15 @@ } }); state.newFeatures.features.ping(); + const tagsStore = state.featureProperties.getStore(newId); { // Set some metainfo - const tagsStore = state.featureProperties.getStore(newId); const properties = tagsStore.data; if (snapTo) { // metatags (starting with underscore) are not uploaded, so we can safely mark this properties["_referencing_ways"] = `["${snapTo}"]`; } + properties["_backend"] = state.osmConnection.Backend() properties["_last_edit:timestamp"] = new Date().toISOString(); const userdetails = state.osmConnection.userDetails.data; properties["_last_edit:contributor"] = userdetails.name; @@ -112,8 +113,9 @@ } const feature = state.indexedFeatures.featuresById.data.get(newId); abort(); - state.selectedElement.setData(feature); state.selectedLayer.setData(selectedPreset.layer); + state.selectedElement.setData(feature); + tagsStore.ping() } diff --git a/UI/Popup/AllTagsPanel.svelte b/UI/Popup/AllTagsPanel.svelte index b2beb2cef0..e2e918bed7 100644 --- a/UI/Popup/AllTagsPanel.svelte +++ b/UI/Popup/AllTagsPanel.svelte @@ -1,46 +1,63 @@
diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index 9a56c1cecb..cc0dcdbfd5 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -99,63 +99,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { }) ), ] - allRenderings.push( - new Toggle( - new Lazy(() => - FeatureInfoBox.createEditElements(questionBoxes, layerConfig, tags, state) - ), - undefined, - state.featureSwitchUserbadge - ) - ) return new Combine(allRenderings).SetClass("block") } - - /** - * All the edit elements, together (note that the question boxes are passed though) - * @param questionBoxes - * @param layerConfig - * @param tags - * @param state - * @private - */ - private static createEditElements( - questionBoxes: Map, - layerConfig: LayerConfig, - tags: UIEventSource, - state: FeaturePipelineState - ): BaseUIElement { - let editElements: BaseUIElement[] = [] - questionBoxes.forEach((questionBox) => { - editElements.push(questionBox) - }) - - editElements.push( - new VariableUiElement( - state.osmConnection.userDetails - .map((ud) => ud.csCount) - .map( - (csCount) => { - if ( - csCount <= Constants.userJourney.historyLinkVisible && - state.featureSwitchIsDebugging.data == false && - state.featureSwitchIsTesting.data === false - ) { - return undefined - } - - return new TagRenderingAnswer( - tags, - SharedTagRenderings.SharedTagRendering.get("last_edit"), - state - ) - }, - [state.featureSwitchIsDebugging, state.featureSwitchIsTesting] - ) - ) - ) - - return new Combine(editElements).SetClass("flex flex-col") - } } diff --git a/UI/Popup/TagRendering/Questionbox.svelte b/UI/Popup/TagRendering/Questionbox.svelte index e8f4dddecd..8bfc71a019 100644 --- a/UI/Popup/TagRendering/Questionbox.svelte +++ b/UI/Popup/TagRendering/Questionbox.svelte @@ -41,7 +41,10 @@ return true; } - const baseQuestions = (layer.tagRenderings ?? [])?.filter(tr => allowed(tr.labels) && tr.question !== undefined); + let baseQuestions = [] + $: { + baseQuestions = (layer.tagRenderings ?? [])?.filter(tr => allowed(tr.labels) && tr.question !== undefined); + } let skippedQuestions = new UIEventSource>(new Set()); let questionsToAsk = tags.map(tags => { @@ -80,6 +83,7 @@ skipped++; } } + $: console.log("Current questionbox state:", {answered, skipped, questionsToAsk, layer, selectedElement, tags}) {#if _questionsToAsk.length === 0} diff --git a/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/UI/Popup/TagRendering/TagRenderingQuestion.svelte index 032627a2df..38e681ae50 100644 --- a/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -32,7 +32,6 @@ checkedMappings = [...config.mappings.map(_ => false), false /*One element extra in case a freeform value is added*/]; } } - $: console.log("Checked mappings:", checkedMappings) let selectedTags: TagsFilter = undefined; function mappingIsHidden(mapping: Mapping): boolean { diff --git a/UI/SpecialVisualization.ts b/UI/SpecialVisualization.ts index a43e382feb..dd0d6db17b 100644 --- a/UI/SpecialVisualization.ts +++ b/UI/SpecialVisualization.ts @@ -63,6 +63,7 @@ export interface SpecialVisualizationState { readonly userRelatedState: { readonly mangroveIdentity: MangroveIdentity readonly showAllQuestionsAtOnce: UIEventSource + readonly preferencesAsTags: Store> } readonly lastClickObject: WritableFeatureSource } diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 652f988182..9e1acdf160 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -1265,21 +1265,24 @@ export default class SpecialVisualizations { doc: "The URL to link to", required: true, }, + { + name: "class", + doc: "CSS-classes to add to the element", + }, ], constr( state: SpecialVisualizationState, tagSource: UIEventSource>, args: string[] ): BaseUIElement { - const [text, href] = args + const [text, href, classnames] = args return new VariableUiElement( - tagSource.map( - (tags) => - new Link( - Utils.SubstituteKeys(text, tags), - Utils.SubstituteKeys(href, tags), - true - ) + tagSource.map((tags) => + new Link( + Utils.SubstituteKeys(text, tags), + Utils.SubstituteKeys(href, tags), + true + ).SetClass(classnames) ) ) }, diff --git a/UI/i18n/Translation.ts b/UI/i18n/Translation.ts index 67c9c84387..72f00dd007 100644 --- a/UI/i18n/Translation.ts +++ b/UI/i18n/Translation.ts @@ -29,7 +29,14 @@ export class Translation extends BaseUIElement { } count++ if (typeof translations[translationsKey] != "string") { - console.error("Non-string object in translation: ", translations[translationsKey]) + console.error( + "Non-string object at", + context, + "in translation: ", + translations[translationsKey], + "\n current translations are: ", + translations + ) throw ( "Error in an object depicting a translation: a non-string object was found. (" + context + diff --git a/Utils.ts b/Utils.ts index 260a01495d..4086627e35 100644 --- a/Utils.ts +++ b/Utils.ts @@ -307,13 +307,21 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be * @param init * @constructor */ - public static AddLazyProperty(object: any, name: string, init: () => any) { + public static AddLazyProperty( + object: any, + name: string, + init: () => any, + whenDone?: () => void + ) { Object.defineProperty(object, name, { enumerable: false, configurable: true, get: () => { delete object[name] object[name] = init() + if (whenDone) { + whenDone() + } return object[name] }, }) @@ -332,6 +340,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be enumerable: false, configurable: true, get: () => { + console.trace("Property", name, "got requested") init().then((r) => { delete object[name] object[name] = r diff --git a/assets/layers/bench/license_info.json b/assets/layers/bench/license_info.json index fab2a775e2..63bb2ba0f2 100644 --- a/assets/layers/bench/license_info.json +++ b/assets/layers/bench/license_info.json @@ -19,4 +19,4 @@ "https://commons.wikimedia.org/wiki/File:ISO_7010_P018.svg" ] } -] +] \ No newline at end of file diff --git a/assets/layers/last_click/last_click.json b/assets/layers/last_click/last_click.json index 6d138a098e..ef84ed86ab 100644 --- a/assets/layers/last_click/last_click.json +++ b/assets/layers/last_click/last_click.json @@ -55,7 +55,16 @@ "*": "{open_note()}" } }, - "all_tags" + { + "metacondition": { + "or": [ + "__featureSwitchDebugging=true" + ] + }, + "render": { + "*": "{all_tags()}" + } + } ], "mapRendering": [ { diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index cbc84fc913..986c1327e3 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -1365,10 +1365,26 @@ ] }, "last_edit": { - "#": "Gives some metainfo about the last edit and who did edit it - rendering only", + "description": "Gives some metainfo about the last edit and who did edit it - rendering only", "condition": "_last_edit:contributor~*", + "metacondition": { + "or": [ + "__featureSwitchIsTesting=true", + "__featureSwitchIsDebugging=true", + "mapcomplete-show_debug=yes", + "_csCount>=10" + ] + }, "render": { - "*": "" + "special": { + "type": "link", + "href": "{_backend}/changeset/{_last_edit:changeset}", + "text": { + "en": "Last edited on {_last_edit:timestamp} by {_last_edit:contributor}", + "nl": "Laatst gewijzigd op {_last_edit:timestamp} door {_last_edit:contributor}" + }, + "class": "subtle font-small" + } } }, "all_tags": { diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 76e4864b2a..b61bfa8821 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -3560,6 +3560,9 @@ "19": { "then": "Aquí es poden reciclar sabates" }, + "20": { + "then": "Aquí es poden reciclar petits aparells elèctrics" + }, "21": { "then": "Aquí es poden reciclar petits aparells elèctrics" }, @@ -4554,4 +4557,4 @@ } } } -} +} \ No newline at end of file diff --git a/langs/layers/de.json b/langs/layers/de.json index fb968e0c74..416e7e10fd 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -6927,13 +6927,13 @@ "16": { "question": "Recycling von Kunststoffen" }, - "18": { + "17": { "question": "Recycling von Metallschrott" }, - "19": { + "18": { "question": "Recycling von Elektrokleingeräten" }, - "20": { + "19": { "question": "Recycling von Restabfällen" }, "20": { diff --git a/langs/layers/en.json b/langs/layers/en.json index e56da389ff..d00a20d2f1 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -6946,15 +6946,12 @@ "question": "Recycling of plastic" }, "17": { - "question": "Recycling of printer cartridges" - }, - "18": { "question": "Recycling of scrap metal" }, - "19": { + "18": { "question": "Recycling of small electrical appliances" }, - "20": { + "19": { "question": "Recycling of residual waste" }, "20": { @@ -9182,4 +9179,4 @@ } } } -} +} \ No newline at end of file diff --git a/langs/layers/es.json b/langs/layers/es.json index 314f0fceb3..b3b31c2da6 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -3448,7 +3448,7 @@ "16": { "question": "Reciclaje de plástico" }, - "18": { + "17": { "question": "Reciclaje de chatarra" }, "18": { diff --git a/langs/layers/it.json b/langs/layers/it.json index 4187552586..b2e9cff656 100644 --- a/langs/layers/it.json +++ b/langs/layers/it.json @@ -1799,13 +1799,13 @@ "16": { "question": "Riciclo di plastica" }, - "18": { + "17": { "question": "Riciclo di rottami metallici" }, - "19": { + "18": { "question": "Riciclo di piccoli elettrodomestici" }, - "20": { + "19": { "question": "Riciclo di secco" }, "20": { diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 74468ce28a..7479891b77 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -6512,14 +6512,14 @@ "question": "Recycling van plastic" }, "17": { - "question": "Recycling van printer cartridges" - }, - "18": { "question": "Recycling van oud metaal" }, - "19": { + "18": { "question": "Recycling van kleine elektrische apparaten" }, + "19": { + "question": "Recycling van restafval" + }, "20": { "question": "Recycling van restafval" } @@ -8652,4 +8652,4 @@ } } } -} +} \ No newline at end of file diff --git a/langs/shared-questions/en.json b/langs/shared-questions/en.json index dadb187b95..2ceea52424 100644 --- a/langs/shared-questions/en.json +++ b/langs/shared-questions/en.json @@ -131,6 +131,13 @@ "question": "What is the network name for the wireless internet access?", "render": "The network name is {internet_access:ssid}" }, + "last_edit": { + "render": { + "special": { + "text": "Last edited on {_last_edit:timestamp} by {_last_edit:contributor}" + } + } + }, "level": { "mappings": { "0": { diff --git a/langs/shared-questions/nl.json b/langs/shared-questions/nl.json index ac5434b2d7..a1a66199ad 100644 --- a/langs/shared-questions/nl.json +++ b/langs/shared-questions/nl.json @@ -131,6 +131,13 @@ "question": "Wat is de netwerknaam voor de draadloze internettoegang?", "render": "De netwerknaam is {internet_access:ssid}" }, + "last_edit": { + "render": { + "special": { + "text": "Laatst gewijzigd op {_last_edit:timestamp} door {_last_edit:contributor} " + } + } + }, "level": { "mappings": { "0": { diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index de201c47f6..eeef143e82 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -16,7 +16,7 @@ import { Translation } from "../UI/i18n/Translation" import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson" import questions from "../assets/tagRenderings/questions.json" import PointRenderingConfigJson from "../Models/ThemeConfig/Json/PointRenderingConfigJson" -import { PrepareLayer } from "../Models/ThemeConfig/Conversion/PrepareLayer" +import { PrepareLayer, RewriteSpecial } from "../Models/ThemeConfig/Conversion/PrepareLayer" import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme" import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion" import { Utils } from "../Utils" @@ -156,6 +156,7 @@ class LayerOverviewUtils extends Script { getSharedTagRenderings(doesImageExist: DoesImageExist): Map { const dict = new Map() + const prep = new RewriteSpecial() const validator = new ValidateTagRenderings(undefined, doesImageExist) for (const key in questions) { if (key === "id") { @@ -163,7 +164,12 @@ class LayerOverviewUtils extends Script { } questions[key].id = key questions[key]["source"] = "shared-questions" - const config = questions[key] + const config = prep.convertStrict( + questions[key], + "questions.json:" + key + ) + delete config.description + delete config["#"] validator.convertStrict( config, "generate-layer-overview:tagRenderings/questions.json:" + key diff --git a/test/Logic/OSM/Actions/ReplaceGeometryAction.spec.ts b/test/Logic/OSM/Actions/ReplaceGeometryAction.spec.ts index 2c9998e8da..fc4b89cd96 100644 --- a/test/Logic/OSM/Actions/ReplaceGeometryAction.spec.ts +++ b/test/Logic/OSM/Actions/ReplaceGeometryAction.spec.ts @@ -883,7 +883,10 @@ describe("ReplaceGeometryAction", () => { const data = await Utils.downloadJson(url) const fullNodeDatabase = undefined // TODO new FullNodeDatabaseSource(undefined) // TODO fullNodeDatabase.handleOsmJson(data, 0) - const changes = new Changes() + const changes = new Changes({ + dryRun: new ImmutableStore(true), + osmConnection: new OsmConnection() + }) const osmConnection = new OsmConnection({ dryRun: new ImmutableStore(true), }) From e5ea4dbfa342627d7703f71a776c3ca6bb00947a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 15 Apr 2023 02:47:48 +0200 Subject: [PATCH 036/257] Fix build --- assets/layers/advertising/advertising.json | 2 +- assets/layers/bench/bench.json | 3 +-- assets/layers/charging_station/charging_station.json | 9 +++++---- assets/layers/gps_location/gps_location.json | 2 +- assets/layers/last_click/last_click.json | 12 +----------- assets/layers/pharmacy/pharmacy.json | 6 +++--- assets/layers/recycling/recycling.json | 2 +- assets/tagRenderings/questions.json | 11 ++++++++--- langs/ca.json | 6 +----- langs/layers/de.json | 3 --- langs/layers/en.json | 2 +- langs/layers/nl.json | 2 +- 12 files changed, 24 insertions(+), 36 deletions(-) diff --git a/assets/layers/advertising/advertising.json b/assets/layers/advertising/advertising.json index 64db86f89e..16a6c00328 100644 --- a/assets/layers/advertising/advertising.json +++ b/assets/layers/advertising/advertising.json @@ -1169,4 +1169,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/assets/layers/bench/bench.json b/assets/layers/bench/bench.json index d1b473a73b..ad1e38049c 100644 --- a/assets/layers/bench/bench.json +++ b/assets/layers/bench/bench.json @@ -82,7 +82,6 @@ "class": "large" } }, - { "if": "backrest=yes", "then": { @@ -1047,4 +1046,4 @@ }, "has_image" ] -} +} \ No newline at end of file diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 464722e83e..89bf9bf748 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -4590,13 +4590,14 @@ ] } }, - "questions", { - "id": "questions_technical", + "id": "questions" + }, + { + "id": "questions", "render": { "en": "

Technical questions

The questions below are very technical. Feel free to ignore them
{questions(technical)}", - "nl": "

Technische vragen

De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt
{questions(technical)}", - "de": "

Technische Fragen

Die folgenden Fragen sind sehr technisch. Sie können sie gerne ignorieren
{questions(technical)}" + "nl": "

Technische vragen

De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt
{questions(technical)}" } } ], diff --git a/assets/layers/gps_location/gps_location.json b/assets/layers/gps_location/gps_location.json index e466f03f58..3c5d8f7ec5 100644 --- a/assets/layers/gps_location/gps_location.json +++ b/assets/layers/gps_location/gps_location.json @@ -36,4 +36,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/assets/layers/last_click/last_click.json b/assets/layers/last_click/last_click.json index ef84ed86ab..5ff05941c4 100644 --- a/assets/layers/last_click/last_click.json +++ b/assets/layers/last_click/last_click.json @@ -54,16 +54,6 @@ "render": { "*": "{open_note()}" } - }, - { - "metacondition": { - "or": [ - "__featureSwitchDebugging=true" - ] - }, - "render": { - "*": "{all_tags()}" - } } ], "mapRendering": [ @@ -168,4 +158,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/assets/layers/pharmacy/pharmacy.json b/assets/layers/pharmacy/pharmacy.json index b2fe1ab394..e80aca3373 100644 --- a/assets/layers/pharmacy/pharmacy.json +++ b/assets/layers/pharmacy/pharmacy.json @@ -174,6 +174,6 @@ } } ], -"deletion": true, -"allowMove": true -} + "deletion": true, + "allowMove": true +} \ No newline at end of file diff --git a/assets/layers/recycling/recycling.json b/assets/layers/recycling/recycling.json index c278dfb334..f689945000 100644 --- a/assets/layers/recycling/recycling.json +++ b/assets/layers/recycling/recycling.json @@ -1386,4 +1386,4 @@ "enableRelocation": true, "enableImproveAccuracy": true } -} +} \ No newline at end of file diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index 986c1327e3..f9dce1b11e 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -1366,7 +1366,12 @@ }, "last_edit": { "description": "Gives some metainfo about the last edit and who did edit it - rendering only", - "condition": "_last_edit:contributor~*", + "condition": { + "and": [ + "_last_edit:contributor~*", + "_last_edit:changeset" + ] + }, "metacondition": { "or": [ "__featureSwitchIsTesting=true", @@ -1381,7 +1386,7 @@ "href": "{_backend}/changeset/{_last_edit:changeset}", "text": { "en": "Last edited on {_last_edit:timestamp} by {_last_edit:contributor}", - "nl": "Laatst gewijzigd op {_last_edit:timestamp} door {_last_edit:contributor}" + "nl": "Laatst gewijzigd op {_last_edit:timestamp} door {_last_edit:contributor} " }, "class": "subtle font-small" } @@ -2053,4 +2058,4 @@ } ] } -} +} \ No newline at end of file diff --git a/langs/ca.json b/langs/ca.json index c6d9e3e321..164ff7966b 100644 --- a/langs/ca.json +++ b/langs/ca.json @@ -695,12 +695,8 @@ "userinfo": { "gotoInbox": "Obre la teva safata d'entrada", "gotoSettings": "Aneu a la vostra configuració a OpenStreetMap.org", - "moveToHome": "Mou el mapa a la vostra ubicació de casa", - "newMessages": "tens missatges nous", "noDescription": "Encara no tens una descripció al teu perfil", - "noDescriptionCallToAction": "Afegeix una descripció del perfil", - "titleNotLoggedIn": "Benvingut", - "welcome": "Benvingut/da {name}" + "noDescriptionCallToAction": "Afegeix una descripció del perfil" }, "validation": { "color": { diff --git a/langs/layers/de.json b/langs/layers/de.json index 416e7e10fd..1c66afdd2d 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -2527,9 +2527,6 @@ "question": "Welche Leistung bietet ein einzelner Stecker vom Typ
Typ 2 mit Kabel (Mennekes)
?", "render": "
Typ 2 mit Kabel (Mennekes)
liefert maximal {socket:type2_cable:output}" }, - "questions_technical": { - "render": "

Technische Fragen

Die folgenden Fragen sind sehr technisch. Sie können sie gerne ignorieren
{questions(technical)}" - }, "ref": { "question": "Welche Kennnummer hat die Ladestation?", "render": "Die Kennziffer ist {ref}" diff --git a/langs/layers/en.json b/langs/layers/en.json index d00a20d2f1..70e7ef30df 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -2527,7 +2527,7 @@ "question": "What power output does a single plug of type
Type 2 with cable (mennekes)
offer?", "render": "
Type 2 with cable (mennekes)
outputs at most {socket:type2_cable:output}" }, - "questions_technical": { + "questions": { "render": "

Technical questions

The questions below are very technical. Feel free to ignore them
{questions(technical)}" }, "ref": { diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 7479891b77..e0fa11e9ce 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -2319,7 +2319,7 @@ "question": "Welk vermogen levert een enkele stekker van type
Type 2 met kabel (J1772)
?", "render": "
Type 2 met kabel (J1772)
levert een vermogen van maximaal {socket:type2_cable:output}" }, - "questions_technical": { + "questions": { "render": "

Technische vragen

De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt
{questions(technical)}" }, "ref": { From 9ce92b8efa478c751c1d62eed46f8d8b85342797 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 15 Apr 2023 02:50:44 +0200 Subject: [PATCH 037/257] Fix condition --- assets/tagRenderings/questions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index f9dce1b11e..5b9cc48fd8 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -1369,7 +1369,7 @@ "condition": { "and": [ "_last_edit:contributor~*", - "_last_edit:changeset" + "_last_edit:changeset~*" ] }, "metacondition": { @@ -2058,4 +2058,4 @@ } ] } -} \ No newline at end of file +} From ee8dbbcf7d8c14f81fe0ce393a74f56d2b89e54d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 15 Apr 2023 02:56:14 +0200 Subject: [PATCH 038/257] refactoring: Add _backend in overpass-source --- Logic/SimpleMetaTagger.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index d1e28704ce..246ba2f4e1 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -189,7 +189,7 @@ class RewriteMetaInfoTags extends SimpleMetaTagger { "_version_number", "_backend", ], - doc: "Information about the last edit of this object.", + doc: "Information about the last edit of this object. This object will actually _rewrite_ some tags for features coming from overpass", }) } @@ -213,6 +213,7 @@ class RewriteMetaInfoTags extends SimpleMetaTagger { move("changeset", "_last_edit:changeset") move("timestamp", "_last_edit:timestamp") move("version", "_version_number") + feature.properties._backend = "https://openstreetmap.org" return movedSomething } } From 43aeb9d191a7ab12a593b2c83fd4ed1d77f159b5 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 15 Apr 2023 03:02:01 +0200 Subject: [PATCH 039/257] Partial update of the docs --- Docs/CalculatedTags.md | 19 +- Docs/Layers/address.md | 34 +- Docs/Layers/advertising.md | 53 +- Docs/Layers/all_streets.md | 48 +- Docs/Layers/ambulancestation.md | 34 +- Docs/Layers/artwork.md | 66 ++- Docs/Layers/atm.md | 64 ++- Docs/Layers/bank.md | 32 ++ Docs/Layers/banks_with_atm.md | 16 +- Docs/Layers/barrier.md | 56 +- Docs/Layers/bench.md | 64 ++- Docs/Layers/bench_at_pt.md | 56 +- Docs/Layers/bicycle_library.md | 56 +- Docs/Layers/bicycle_rental.md | 66 ++- Docs/Layers/bicycle_rental_non_docking.md | 50 +- Docs/Layers/bicycle_tube_vending_machine.md | 66 ++- Docs/Layers/bike_cafe.md | 36 +- Docs/Layers/bike_cleaning.md | 66 ++- Docs/Layers/bike_parking.md | 66 ++- Docs/Layers/bike_repair_station.md | 66 ++- Docs/Layers/bike_shop.md | 46 +- Docs/Layers/bike_themed_object.md | 36 +- Docs/Layers/binocular.md | 66 ++- Docs/Layers/birdhide.md | 64 ++- Docs/Layers/cafe_pub.md | 64 ++- Docs/Layers/car_rental.md | 44 +- Docs/Layers/caravansites.md | 48 +- Docs/Layers/charging_station.md | 160 ++++-- Docs/Layers/charging_station_ebikes.md | 148 ++++-- Docs/Layers/climbing.md | 34 +- Docs/Layers/climbing_area.md | 26 +- Docs/Layers/climbing_club.md | 34 +- Docs/Layers/climbing_gym.md | 36 +- Docs/Layers/climbing_opportunity.md | 34 +- Docs/Layers/climbing_route.md | 36 +- Docs/Layers/clock.md | 66 ++- Docs/Layers/crab_address.md | 34 +- Docs/Layers/crossings.md | 34 +- .../cultural_places_without_etymology.md | 14 +- Docs/Layers/cycleways_and_roads.md | 44 +- Docs/Layers/defibrillator.md | 64 ++- Docs/Layers/dentist.md | 64 ++- Docs/Layers/direction.md | 42 +- Docs/Layers/doctors.md | 64 ++- Docs/Layers/dogfoodb.md | 50 +- Docs/Layers/dogpark.md | 36 +- Docs/Layers/dogshop.md | 58 +- Docs/Layers/drinking_water.md | 66 ++- Docs/Layers/dumpstations.md | 58 +- ...ducation_institutions_without_etymology.md | 14 +- Docs/Layers/elevator.md | 38 +- Docs/Layers/entrance.md | 36 +- Docs/Layers/etymology.md | 14 +- Docs/Layers/extinguisher.md | 34 +- Docs/Layers/facadegardens.md | 50 +- Docs/Layers/fietsstraat.md | 48 +- Docs/Layers/fire_station.md | 34 +- Docs/Layers/fitness_centre.md | 34 +- Docs/Layers/fitness_station.md | 34 +- Docs/Layers/food.md | 64 ++- Docs/Layers/friture.md | 50 +- Docs/Layers/ghost_bike.md | 66 ++- Docs/Layers/governments.md | 36 +- Docs/Layers/hackerspace.md | 38 +- ...lth_and_social_places_without_etymology.md | 14 +- Docs/Layers/hospital.md | 34 +- Docs/Layers/hotel.md | 48 +- Docs/Layers/hydrant.md | 34 +- Docs/Layers/indoors.md | 36 +- Docs/Layers/information_board.md | 64 ++- Docs/Layers/kerbs.md | 34 +- Docs/Layers/kindergarten_childcare.md | 64 ++- Docs/Layers/lit_streets.md | 30 +- Docs/Layers/map.md | 66 ++- Docs/Layers/maproulette.md | 32 ++ Docs/Layers/maproulette_challenge.md | 32 ++ Docs/Layers/maxspeed.md | 44 +- Docs/Layers/medical-shops.md | 58 +- Docs/Layers/nature_reserve.md | 36 +- Docs/Layers/observation_tower.md | 48 +- Docs/Layers/osm_community_index.md | 32 ++ Docs/Layers/parcel_lockers.md | 64 ++- Docs/Layers/parking.md | 66 ++- Docs/Layers/parking_spaces.md | 36 +- Docs/Layers/parking_ticket_machine.md | 36 +- .../parks_and_forests_without_etymology.md | 14 +- Docs/Layers/pharmacy.md | 64 ++- Docs/Layers/physiotherapist.md | 64 ++- Docs/Layers/picnic_table.md | 66 ++- Docs/Layers/play_forest.md | 26 +- Docs/Layers/playground.md | 46 +- Docs/Layers/postboxes.md | 46 +- Docs/Layers/postoffices.md | 24 +- Docs/Layers/public_bookcase.md | 64 ++- Docs/Layers/railway_platforms.md | 34 +- Docs/Layers/rainbow_crossing_high_zoom.md | 20 +- Docs/Layers/rainbow_crossings.md | 36 +- Docs/Layers/reception_desk.md | 38 +- Docs/Layers/recycling.md | 74 ++- Docs/Layers/school.md | 34 +- Docs/Layers/shelter.md | 34 +- Docs/Layers/shops.md | 54 +- Docs/Layers/slow_roads.md | 36 +- Docs/Layers/speed_camera.md | 34 +- Docs/Layers/speed_display.md | 34 +- Docs/Layers/sport_pitch.md | 24 +- Docs/Layers/sport_places_without_etymology.md | 14 +- Docs/Layers/sport_shops.md | 58 +- Docs/Layers/sports_centre.md | 44 +- Docs/Layers/stairs.md | 36 +- Docs/Layers/street_lamps.md | 66 ++- Docs/Layers/streets_without_etymology.md | 14 +- Docs/Layers/surveillance_camera.md | 36 +- Docs/Layers/tertiary_education.md | 34 +- Docs/Layers/ticket_machine.md | 36 +- Docs/Layers/ticket_validator.md | 36 +- Docs/Layers/toekomstige_fietsstraat.md | 48 +- Docs/Layers/toilet.md | 64 ++- Docs/Layers/toilet_at_amenity.md | 54 +- .../toursistic_places_without_etymology.md | 14 +- Docs/Layers/trail.md | 36 +- Docs/Layers/transit_routes.md | 34 +- Docs/Layers/transit_stops.md | 34 +- Docs/Layers/tree_node.md | 66 ++- Docs/Layers/veterinary.md | 36 +- Docs/Layers/viewpoint.md | 36 +- Docs/Layers/village_green.md | 36 +- Docs/Layers/visitor_information_centre.md | 34 +- Docs/Layers/walls_and_buildings.md | 36 +- Docs/Layers/waste_basket.md | 64 ++- Docs/Layers/waste_disposal.md | 64 ++- Docs/Layers/windturbine.md | 34 +- Docs/SpecialRenderings.md | 318 +++++++---- Docs/Themes/advertising.md | 2 + Docs/Themes/aed.md | 2 + Docs/Themes/artwork.md | 2 + Docs/Themes/atm.md | 2 + Docs/Themes/bag.md | 3 +- Docs/Themes/benches.md | 2 + Docs/Themes/bicycle_rental.md | 2 + Docs/Themes/bicyclelib.md | 2 + Docs/Themes/binoculars.md | 2 + Docs/Themes/blind_osm.md | 2 + Docs/Themes/bookcases.md | 2 + Docs/Themes/buurtnatuur.md | 2 + Docs/Themes/cafes_and_pubs.md | 2 + Docs/Themes/campersite.md | 2 + Docs/Themes/charging_stations.md | 2 + Docs/Themes/climbing.md | 2 + Docs/Themes/clock.md | 2 + Docs/Themes/cycle_highways.md | 2 + Docs/Themes/cycle_infra.md | 2 + Docs/Themes/cyclenodes.md | 2 + Docs/Themes/cyclestreets.md | 2 + Docs/Themes/cyclofix.md | 2 + Docs/Themes/drinking_water.md | 2 + Docs/Themes/education.md | 2 + Docs/Themes/etymology.md | 2 + Docs/Themes/facadegardens.md | 2 + Docs/Themes/food.md | 2 + Docs/Themes/fritures.md | 2 + Docs/Themes/fruit_trees.md | 2 + Docs/Themes/ghostbikes.md | 2 + Docs/Themes/grb.md | 3 +- Docs/Themes/grb_fixme.md | 2 + Docs/Themes/hackerspaces.md | 2 + Docs/Themes/hailhydrant.md | 2 + Docs/Themes/healthcare.md | 2 + Docs/Themes/hotels.md | 2 + Docs/Themes/indoors.md | 2 + Docs/Themes/kerbs_and_crossings.md | 2 + Docs/Themes/mapcomplete-changes.md | 2 + Docs/Themes/maproulette.md | 2 + Docs/Themes/maps.md | 2 + Docs/Themes/maxspeed.md | 2 + Docs/Themes/nature.md | 2 + Docs/Themes/natuurpunt.md | 2 + Docs/Themes/notes.md | 2 + Docs/Themes/observation_towers.md | 2 + Docs/Themes/onwheels.md | 2 + Docs/Themes/openwindpowermap.md | 2 + Docs/Themes/osm_community_index.md | 2 + Docs/Themes/parkings.md | 2 + Docs/Themes/personal.md | 6 + Docs/Themes/pets.md | 2 + Docs/Themes/play_forests.md | 2 + Docs/Themes/playgrounds.md | 2 + Docs/Themes/postal_codes.md | 2 + Docs/Themes/postboxes.md | 2 + Docs/Themes/rainbow_crossings.md | 2 + Docs/Themes/shops.md | 2 + Docs/Themes/sidewalks.md | 2 + Docs/Themes/speelplekken.md | 3 +- Docs/Themes/sport_pitches.md | 2 + Docs/Themes/sports.md | 2 + Docs/Themes/stations.md | 2 + Docs/Themes/street_lighting.md | 2 + Docs/Themes/street_lighting_assen.md | 2 + Docs/Themes/surveillance.md | 2 + Docs/Themes/toerisme_vlaanderen.md | 2 + Docs/Themes/toilets.md | 2 + Docs/Themes/transit.md | 2 + Docs/Themes/trees.md | 2 + Docs/Themes/uk_addresses.md | 2 + Docs/Themes/walls_and_buildings.md | 2 + Docs/Themes/waste.md | 2 + Docs/Themes/waste_assen.md | 2 + Docs/Themes/waste_basket.md | 2 + Docs/Themes/width.md | 2 + Docs/wikiIndex.txt | 499 ------------------ 210 files changed, 5787 insertions(+), 1201 deletions(-) diff --git a/Docs/CalculatedTags.md b/Docs/CalculatedTags.md index 697cda5701..1639075a12 100644 --- a/Docs/CalculatedTags.md +++ b/Docs/CalculatedTags.md @@ -18,7 +18,7 @@ + [_isOpen](#_isopen) + [_direction:numerical, _direction:leftright](#_directionnumerical,-_direction:leftright) + [_direction:centerpoint](#_directioncenterpoint) - + [_now:date, _now:datetime, _loaded:date, _loaded:_datetime](#_nowdate,-_now:datetime,-_loaded:date,-_loaded:_datetime) + + [_now:date, _now:datetime](#_nowdate,-_now:datetime) + [_last_edit:contributor, _last_edit:contributor:uid, _last_edit:changeset, _last_edit:timestamp, _version_number, _backend](#_last_editcontributor,-_last_edit:contributor:uid,-_last_edit:changeset,-_last_edit:timestamp,-_version_number,-_backend) + [sidewalk:left, sidewalk:right, generic_key:left:property, generic_key:right:property](#sidewalkleft,-sidewalk:right,-generic_key:left:property,-generic_key:right:property) + [_geometry:type](#_geometrytype) @@ -30,7 +30,6 @@ + [intersectionsWith](#intersectionswith) + [closest](#closest) + [closestn](#closestn) - + [memberships](#memberships) + [get](#get) @@ -142,7 +141,7 @@ This is a lazy metatag and is only calculated when needed -### _now:date, _now:datetime, _loaded:date, _loaded:_datetime +### _now:date, _now:datetime @@ -156,7 +155,7 @@ Adds the time that the data got loaded - pretty much the time of downloading fro -Information about the last edit of this object. +Information about the last edit of this object. This object will actually _rewrite_ some tags for features coming from overpass @@ -196,7 +195,7 @@ Extract the 'level'-tag into a normalized, ';'-separated value -_referencing_ways contains - for a node - which ways use this this node as point in their geometry. If the preset has 'snapToLayer' defined, the icon will be calculated based on the preset tags with `_referencing_ways=["way/-1"]` added. +_referencing_ways contains - for a node - which ways use this this node as point in their geometry. This is a lazy metatag and is only calculated when needed @@ -256,7 +255,6 @@ Some advanced functions are available on **feat** as well: - [intersectionsWith](#intersectionsWith) - [closest](#closest) - [closestn](#closestn) - - [memberships](#memberships) - [get](#get) @@ -324,15 +322,6 @@ If a 'unique tag key' is given, the tag with this key will only appear once (e.g 3. maxDistanceInMeters (optional) -### memberships - - Gives a list of `{role: string, relation: Relation}`-objects, containing all the relations that this feature is part of. - -For example: `_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')` - - - - ### get Gets the property of the feature, parses it (as JSON) and returns it. Might return 'undefined' if not defined, null, ... diff --git a/Docs/Layers/address.md b/Docs/Layers/address.md index 59ea54e7b9..98270cfecc 100644 --- a/Docs/Layers/address.md +++ b/Docs/Layers/address.md @@ -111,6 +111,38 @@ This is rendered with `Fixme description{fixme}` - *No fixme - write something here to explain complicated cases* corresponds with `` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/address/address.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/address/address.json) diff --git a/Docs/Layers/advertising.md b/Docs/Layers/advertising.md index 1ffb2594bc..3321cea16c 100644 --- a/Docs/Layers/advertising.md +++ b/Docs/Layers/advertising.md @@ -31,6 +31,7 @@ We will complete data from advertising features with reference, operator and lit - [advertising](https://mapcomplete.osm.be/advertising) + - [personal](https://mapcomplete.osm.be/personal) @@ -77,8 +78,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -232,12 +231,10 @@ This is rendered with `Reference number is {ref}` -### questions +### leftover-questions -Show the images block at this location - This tagrendering has no question and is thus read-only @@ -248,10 +245,50 @@ This tagrendering has no question and is thus read-only -Shows a small map with the feature. Added by default to every popup +This tagrendering has no question and is thus read-only + + + + + +### move-button + + This tagrendering has no question and is thus read-only - -This document is autogenerated from [assets/themes/advertising/advertising.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/advertising/advertising.json) + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + +This document is autogenerated from [assets/layers/advertising/advertising.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/advertising/advertising.json) diff --git a/Docs/Layers/all_streets.md b/Docs/Layers/all_streets.md index 6b872c4e21..7bf1840513 100644 --- a/Docs/Layers/all_streets.md +++ b/Docs/Layers/all_streets.md @@ -70,7 +70,15 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + This tagrendering has no question and is thus read-only @@ -78,6 +86,28 @@ This tagrendering has no question and is thus read-only +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + ### is_cyclestreet @@ -137,24 +167,10 @@ This tagrendering is only visible in the popup if the following condition is met -### questions +### split-button -Show the images block at this location - -This tagrendering has no question and is thus read-only - - - - - -### minimap - - - -Shows a small map with the feature. Added by default to every popup - This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/ambulancestation.md b/Docs/Layers/ambulancestation.md index 7391a9d9f8..80103dc295 100644 --- a/Docs/Layers/ambulancestation.md +++ b/Docs/Layers/ambulancestation.md @@ -156,10 +156,40 @@ This is rendered with `The operator is a(n) {operator:type} entity.` -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + This tagrendering has no question and is thus read-only - + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/ambulancestation/ambulancestation.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/ambulancestation/ambulancestation.json) diff --git a/Docs/Layers/artwork.md b/Docs/Layers/artwork.md index 6aad555a4e..b5fbd2a055 100644 --- a/Docs/Layers/artwork.md +++ b/Docs/Layers/artwork.md @@ -83,8 +83,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -176,8 +174,6 @@ This tagrendering has labels `artwork-question` -Shows a wikipedia box with the corresponding wikipedia article; the wikidata-item link can be changed by a contributor - The question is *What is the corresponding Wikidata entity?* This rendering asks information about the property [wikidata](https://wiki.openstreetmap.org/wiki/Key:wikidata) @@ -420,6 +416,68 @@ This tagrendering has labels `bench-questions` +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/atm.md b/Docs/Layers/atm.md index 59efb276ef..f61f0a87f6 100644 --- a/Docs/Layers/atm.md +++ b/Docs/Layers/atm.md @@ -74,8 +74,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -200,6 +198,68 @@ This tagrendering is only visible in the popup if the following condition is met +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/bank.md b/Docs/Layers/bank.md index c6291208eb..ed5dd64f11 100644 --- a/Docs/Layers/bank.md +++ b/Docs/Layers/bank.md @@ -82,6 +82,38 @@ The question is *Does this bank have an ATM?* +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/banks_with_atm.md b/Docs/Layers/banks_with_atm.md index d6139e364a..9a88b41a9c 100644 --- a/Docs/Layers/banks_with_atm.md +++ b/Docs/Layers/banks_with_atm.md @@ -83,12 +83,10 @@ The question is *Does this bank have an ATM?* -### questions +### leftover-questions -Show the images block at this location - This tagrendering has no question and is thus read-only @@ -99,12 +97,22 @@ This tagrendering has no question and is thus read-only -Shows a small map with the feature. Added by default to every popup +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + This tagrendering has no question and is thus read-only +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + #### Filters diff --git a/Docs/Layers/barrier.md b/Docs/Layers/barrier.md index 9bee4b9906..60abb5193f 100644 --- a/Docs/Layers/barrier.md +++ b/Docs/Layers/barrier.md @@ -78,8 +78,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -221,6 +219,58 @@ This is rendered with `Overlap: {overlap} m` -This tagrendering is only visible in the popup if the following condition is met: `cycle_barrier=double|cycle_barrier=triple` +This tagrendering is only visible in the popup if the following condition is met: `cycle_barrier=double|cycle_barrier=triple` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/barrier/barrier.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/barrier/barrier.json) diff --git a/Docs/Layers/bench.md b/Docs/Layers/bench.md index 5bb4cf217f..f883f0e8e6 100644 --- a/Docs/Layers/bench.md +++ b/Docs/Layers/bench.md @@ -83,8 +83,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -388,6 +386,68 @@ This tagrendering has labels `artwork-question` +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/bench_at_pt.md b/Docs/Layers/bench_at_pt.md index ea47eddd1d..5d1235ff29 100644 --- a/Docs/Layers/bench_at_pt.md +++ b/Docs/Layers/bench_at_pt.md @@ -71,8 +71,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -106,6 +104,58 @@ The question is *What kind of bench is this?* - *There is a normal, sit-down bench here* corresponds with `bench=yes` - *Stand up bench* corresponds with `bench=stand_up_bench` - *There is no bench here* corresponds with `bench=no` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/bench_at_pt/bench_at_pt.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/bench_at_pt/bench_at_pt.json) diff --git a/Docs/Layers/bicycle_library.md b/Docs/Layers/bicycle_library.md index a730f40765..b79a9492c5 100644 --- a/Docs/Layers/bicycle_library.md +++ b/Docs/Layers/bicycle_library.md @@ -77,8 +77,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -220,6 +218,58 @@ This rendering asks information about the property [description](https://wiki.o This is rendered with `{description}` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/bicycle_library/bicycle_library.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/bicycle_library/bicycle_library.json) diff --git a/Docs/Layers/bicycle_rental.md b/Docs/Layers/bicycle_rental.md index cfc2da80ff..99e92452a2 100644 --- a/Docs/Layers/bicycle_rental.md +++ b/Docs/Layers/bicycle_rental.md @@ -81,8 +81,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -379,6 +377,68 @@ This is rendered with `{capacity:tandem_bicycle} tandem can be rented here` This tagrendering is only visible in the popup if the following condition is met: `rental~^(.*tandem_bicycle.*)$` -This tagrendering has labels `bicycle_rental` +This tagrendering has labels `bicycle_rental` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/bicycle_rental/bicycle_rental.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/bicycle_rental/bicycle_rental.json) diff --git a/Docs/Layers/bicycle_rental_non_docking.md b/Docs/Layers/bicycle_rental_non_docking.md index 5e01025e1a..ec48f1214e 100644 --- a/Docs/Layers/bicycle_rental_non_docking.md +++ b/Docs/Layers/bicycle_rental_non_docking.md @@ -80,8 +80,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -382,12 +380,10 @@ This tagrendering has labels `bicycle_rental` -### questions +### leftover-questions -Show the images block at this location - This tagrendering has no question and is thus read-only @@ -398,10 +394,50 @@ This tagrendering has no question and is thus read-only -Shows a small map with the feature. Added by default to every popup +This tagrendering has no question and is thus read-only + + + + + +### move-button + + This tagrendering has no question and is thus read-only - + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/themes/cyclofix/cyclofix.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/cyclofix/cyclofix.json) diff --git a/Docs/Layers/bicycle_tube_vending_machine.md b/Docs/Layers/bicycle_tube_vending_machine.md index 4b04673963..2b3d58bca2 100644 --- a/Docs/Layers/bicycle_tube_vending_machine.md +++ b/Docs/Layers/bicycle_tube_vending_machine.md @@ -73,8 +73,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -196,6 +194,68 @@ The question is *Are other bicycle bicycle accessories sold here?* - Unselecting this answer will add vending:bicycle_pump=no - *Bicycle locks are sold here* corresponds with `vending:bicycle_lock=yes` - Unselecting this answer will add vending:bicycle_lock=no - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json) diff --git a/Docs/Layers/bike_cafe.md b/Docs/Layers/bike_cafe.md index 80f0240a57..3a0ec35ba3 100644 --- a/Docs/Layers/bike_cafe.md +++ b/Docs/Layers/bike_cafe.md @@ -77,8 +77,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -217,6 +215,38 @@ This rendering asks information about the property [opening_hours](https://wiki This is rendered with `

Opening hours

{opening_hours_table(opening_hours)}` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/bike_cafe/bike_cafe.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/bike_cafe/bike_cafe.json) diff --git a/Docs/Layers/bike_cleaning.md b/Docs/Layers/bike_cleaning.md index a695f5b5a6..bb036c4184 100644 --- a/Docs/Layers/bike_cleaning.md +++ b/Docs/Layers/bike_cleaning.md @@ -70,8 +70,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -117,6 +115,68 @@ This is rendered with `Using the cleaning service costs {charge}` - *This cleaning service is paid* corresponds with `fee=yes` -This tagrendering is only visible in the popup if the following condition is met: `amenity=bike_wash|amenity=bicycle_wash` +This tagrendering is only visible in the popup if the following condition is met: `amenity=bike_wash|amenity=bicycle_wash` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/bike_cleaning/bike_cleaning.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/bike_cleaning/bike_cleaning.json) diff --git a/Docs/Layers/bike_parking.md b/Docs/Layers/bike_parking.md index f7dd3441c5..f2bacddbf0 100644 --- a/Docs/Layers/bike_parking.md +++ b/Docs/Layers/bike_parking.md @@ -76,8 +76,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -209,6 +207,68 @@ This is rendered with `This parking fits {capacity:cargo_bike} cargo bikes` -This tagrendering is only visible in the popup if the following condition is met: `cargo_bike~^(designated|yes)$` +This tagrendering is only visible in the popup if the following condition is met: `cargo_bike~^(designated|yes)$` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/bike_parking/bike_parking.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/bike_parking/bike_parking.json) diff --git a/Docs/Layers/bike_repair_station.md b/Docs/Layers/bike_repair_station.md index 6115370125..212c675b7d 100644 --- a/Docs/Layers/bike_repair_station.md +++ b/Docs/Layers/bike_repair_station.md @@ -81,8 +81,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -339,6 +337,68 @@ This is rendered with `Located on the {level}th floor` - This option cannot be chosen as answer - *Located on the first floor* corresponds with `level=1` - *Located on the first basement level* corresponds with `level=-1` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/bike_repair_station/bike_repair_station.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/bike_repair_station/bike_repair_station.json) diff --git a/Docs/Layers/bike_shop.md b/Docs/Layers/bike_shop.md index 5ca275fcf3..665c6aafe7 100644 --- a/Docs/Layers/bike_shop.md +++ b/Docs/Layers/bike_shop.md @@ -92,8 +92,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -524,6 +522,48 @@ This rendering asks information about the property [description](https://wiki.o This is rendered with `{description}` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/bike_shop/bike_shop.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/bike_shop/bike_shop.json) diff --git a/Docs/Layers/bike_themed_object.md b/Docs/Layers/bike_themed_object.md index 2db33554ae..5d614c1d78 100644 --- a/Docs/Layers/bike_themed_object.md +++ b/Docs/Layers/bike_themed_object.md @@ -73,8 +73,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -165,6 +163,38 @@ This rendering asks information about the property [opening_hours](https://wiki This is rendered with `

Opening hours

{opening_hours_table(opening_hours)}` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/bike_themed_object/bike_themed_object.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/bike_themed_object/bike_themed_object.json) diff --git a/Docs/Layers/binocular.md b/Docs/Layers/binocular.md index b6090611e0..8e0196ea10 100644 --- a/Docs/Layers/binocular.md +++ b/Docs/Layers/binocular.md @@ -70,8 +70,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -107,6 +105,68 @@ This rendering asks information about the property [direction](https://wiki.ope This is rendered with `Looks towards {direction}°` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/binocular/binocular.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/binocular/binocular.json) diff --git a/Docs/Layers/birdhide.md b/Docs/Layers/birdhide.md index 0e7edcd05d..6cd86a4b59 100644 --- a/Docs/Layers/birdhide.md +++ b/Docs/Layers/birdhide.md @@ -71,8 +71,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -136,6 +134,68 @@ This is rendered with `Operated by {operator}` +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/cafe_pub.md b/Docs/Layers/cafe_pub.md index 9a83203c2a..ad4bfe782a 100644 --- a/Docs/Layers/cafe_pub.md +++ b/Docs/Layers/cafe_pub.md @@ -83,8 +83,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -377,7 +375,15 @@ This tagrendering is only visible in the popup if the following condition is met -Shows the reviews module (including the possibility to leave a review) +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + This tagrendering has no question and is thus read-only @@ -385,6 +391,58 @@ This tagrendering has no question and is thus read-only +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/car_rental.md b/Docs/Layers/car_rental.md index 230f666f58..f96fcd0528 100644 --- a/Docs/Layers/car_rental.md +++ b/Docs/Layers/car_rental.md @@ -61,8 +61,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -162,6 +160,48 @@ This is rendered with `

Opening hours

{opening_hours_table(opening_hours +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/caravansites.md b/Docs/Layers/caravansites.md index de51b0bf34..8aeea4d721 100644 --- a/Docs/Layers/caravansites.md +++ b/Docs/Layers/caravansites.md @@ -81,8 +81,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -276,7 +274,15 @@ This tagrendering has no question and is thus read-only -Shows the reviews module (including the possibility to leave a review) +This tagrendering has no question and is thus read-only + + + + + +### minimap + + This tagrendering has no question and is thus read-only @@ -284,6 +290,18 @@ This tagrendering has no question and is thus read-only +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + ### operator @@ -310,30 +328,6 @@ The question is *Does this place have a power supply?* - *This place has a power supply* corresponds with `power_supply=yes` - *This place does not have power supply* corresponds with `power_supply=no` - - - - -### questions - - - -Show the images block at this location - -This tagrendering has no question and is thus read-only - - - - - -### minimap - - - -Shows a small map with the feature. Added by default to every popup - -This tagrendering has no question and is thus read-only - This document is autogenerated from [assets/themes/campersite/campersite.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/campersite/campersite.json) diff --git a/Docs/Layers/charging_station.md b/Docs/Layers/charging_station.md index 460d2f84e7..6cfadb0287 100644 --- a/Docs/Layers/charging_station.md +++ b/Docs/Layers/charging_station.md @@ -149,8 +149,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -572,7 +570,7 @@ This is rendered with `
Schuko wall plu This tagrendering is only visible in the popup if the following condition is met: `socket:schuko~.+&socket:schuko!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -595,7 +593,7 @@ This is rendered with `
Schuko wall plu This tagrendering is only visible in the popup if the following condition is met: `socket:schuko~.+&socket:schuko!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -618,7 +616,7 @@ This is rendered with `
Schuko wall plu This tagrendering is only visible in the popup if the following condition is met: `socket:schuko~.+&socket:schuko!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -641,7 +639,7 @@ This is rendered with `
European wall p This tagrendering is only visible in the popup if the following condition is met: `socket:typee~.+&socket:typee!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -664,7 +662,7 @@ This is rendered with `
European wall p This tagrendering is only visible in the popup if the following condition is met: `socket:typee~.+&socket:typee!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -688,7 +686,7 @@ This is rendered with `
European wall p This tagrendering is only visible in the popup if the following condition is met: `socket:typee~.+&socket:typee!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -711,7 +709,7 @@ This is rendered with `
Chademo This tagrendering is only visible in the popup if the following condition is met: `socket:chademo~.+&socket:chademo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -734,7 +732,7 @@ This is rendered with `
Chademo This tagrendering is only visible in the popup if the following condition is met: `socket:chademo~.+&socket:chademo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -757,7 +755,7 @@ This is rendered with `
Chademo This tagrendering is only visible in the popup if the following condition is met: `socket:chademo~.+&socket:chademo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -781,7 +779,7 @@ This is rendered with `
Type 1 with cab This tagrendering is only visible in the popup if the following condition is met: `socket:type1_cable~.+&socket:type1_cable!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -804,7 +802,7 @@ This is rendered with `
Type 1 with cab This tagrendering is only visible in the popup if the following condition is met: `socket:type1_cable~.+&socket:type1_cable!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -828,7 +826,7 @@ This is rendered with `
Type 1 with cab This tagrendering is only visible in the popup if the following condition is met: `socket:type1_cable~.+&socket:type1_cable!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -852,7 +850,7 @@ This is rendered with `
Type 1 witho This tagrendering is only visible in the popup if the following condition is met: `socket:type1~.+&socket:type1!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -875,7 +873,7 @@ This is rendered with `
Type 1 witho This tagrendering is only visible in the popup if the following condition is met: `socket:type1~.+&socket:type1!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -901,7 +899,7 @@ This is rendered with `
Type 1 witho This tagrendering is only visible in the popup if the following condition is met: `socket:type1~.+&socket:type1!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -925,7 +923,7 @@ This is rendered with `
Type 1 CCS This tagrendering is only visible in the popup if the following condition is met: `socket:type1_combo~.+&socket:type1_combo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -949,7 +947,7 @@ This is rendered with `
Type 1 CCS This tagrendering is only visible in the popup if the following condition is met: `socket:type1_combo~.+&socket:type1_combo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -975,7 +973,7 @@ This is rendered with `
Type 1 CCS This tagrendering is only visible in the popup if the following condition is met: `socket:type1_combo~.+&socket:type1_combo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -998,7 +996,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_supercharger~.+&socket:tesla_supercharger!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1022,7 +1020,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_supercharger~.+&socket:tesla_supercharger!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1047,7 +1045,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_supercharger~.+&socket:tesla_supercharger!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1071,7 +1069,7 @@ This is rendered with `
Type 2 (men This tagrendering is only visible in the popup if the following condition is met: `socket:type2~.+&socket:type2!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1095,7 +1093,7 @@ This is rendered with `
Type 2 (men This tagrendering is only visible in the popup if the following condition is met: `socket:type2~.+&socket:type2!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1119,7 +1117,7 @@ This is rendered with `
Type 2 (men This tagrendering is only visible in the popup if the following condition is met: `socket:type2~.+&socket:type2!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1143,7 +1141,7 @@ This is rendered with `
Type 2 CCS This tagrendering is only visible in the popup if the following condition is met: `socket:type2_combo~.+&socket:type2_combo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1167,7 +1165,7 @@ This is rendered with `
Type 2 CCS This tagrendering is only visible in the popup if the following condition is met: `socket:type2_combo~.+&socket:type2_combo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1190,7 +1188,7 @@ This is rendered with `
Type 2 CCS This tagrendering is only visible in the popup if the following condition is met: `socket:type2_combo~.+&socket:type2_combo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1214,7 +1212,7 @@ This is rendered with `
Type 2 with cab This tagrendering is only visible in the popup if the following condition is met: `socket:type2_cable~.+&socket:type2_cable!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1238,7 +1236,7 @@ This is rendered with `
Type 2 with cab This tagrendering is only visible in the popup if the following condition is met: `socket:type2_cable~.+&socket:type2_cable!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1262,7 +1260,7 @@ This is rendered with `
Type 2 with cab This tagrendering is only visible in the popup if the following condition is met: `socket:type2_cable~.+&socket:type2_cable!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1286,7 +1284,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_supercharger_ccs~.+&socket:tesla_supercharger_ccs!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1310,7 +1308,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_supercharger_ccs~.+&socket:tesla_supercharger_ccs!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1333,7 +1331,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_supercharger_ccs~.+&socket:tesla_supercharger_ccs!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1356,7 +1354,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_destination~.+&socket:tesla_destination!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1380,7 +1378,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_destination~.+&socket:tesla_destination!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1405,7 +1403,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_destination~.+&socket:tesla_destination!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1429,7 +1427,7 @@ This is rendered with `
Tesla superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_destination~.+&socket:tesla_destination!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1453,7 +1451,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_destination~.+&socket:tesla_destination!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1477,7 +1475,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_destination~.+&socket:tesla_destination!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1500,7 +1498,7 @@ This is rendered with `
USB to char This tagrendering is only visible in the popup if the following condition is met: `socket:USB-A~.+&socket:USB-A!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1524,7 +1522,7 @@ This is rendered with `
USB to char This tagrendering is only visible in the popup if the following condition is met: `socket:USB-A~.+&socket:USB-A!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1548,7 +1546,7 @@ This is rendered with `
USB to char This tagrendering is only visible in the popup if the following condition is met: `socket:USB-A~.+&socket:USB-A!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1571,7 +1569,7 @@ This is rendered with `
Bosch Active Co This tagrendering is only visible in the popup if the following condition is met: `socket:bosch_3pin~.+&socket:bosch_3pin!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1594,7 +1592,7 @@ This is rendered with `
Bosch Active Co This tagrendering is only visible in the popup if the following condition is met: `socket:bosch_3pin~.+&socket:bosch_3pin!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1617,7 +1615,7 @@ This is rendered with `
Bosch Active Co This tagrendering is only visible in the popup if the following condition is met: `socket:bosch_3pin~.+&socket:bosch_3pin!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1640,7 +1638,7 @@ This is rendered with `
Bosch Active Co This tagrendering is only visible in the popup if the following condition is met: `socket:bosch_5pin~.+&socket:bosch_5pin!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1663,7 +1661,7 @@ This is rendered with `
Bosch Active Co This tagrendering is only visible in the popup if the following condition is met: `socket:bosch_5pin~.+&socket:bosch_5pin!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1686,7 +1684,7 @@ This is rendered with `
Bosch Active Co This tagrendering is only visible in the popup if the following condition is met: `socket:bosch_5pin~.+&socket:bosch_5pin!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -2018,7 +2016,67 @@ This tagrendering has no question and is thus read-only -This tagrendering is part of group `technical` + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` diff --git a/Docs/Layers/charging_station_ebikes.md b/Docs/Layers/charging_station_ebikes.md index dde3de5536..f45fc477f0 100644 --- a/Docs/Layers/charging_station_ebikes.md +++ b/Docs/Layers/charging_station_ebikes.md @@ -148,8 +148,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -571,7 +569,7 @@ This is rendered with `
Schuko wall plu This tagrendering is only visible in the popup if the following condition is met: `socket:schuko~.+&socket:schuko!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -594,7 +592,7 @@ This is rendered with `
Schuko wall plu This tagrendering is only visible in the popup if the following condition is met: `socket:schuko~.+&socket:schuko!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -617,7 +615,7 @@ This is rendered with `
Schuko wall plu This tagrendering is only visible in the popup if the following condition is met: `socket:schuko~.+&socket:schuko!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -640,7 +638,7 @@ This is rendered with `
European wall p This tagrendering is only visible in the popup if the following condition is met: `socket:typee~.+&socket:typee!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -663,7 +661,7 @@ This is rendered with `
European wall p This tagrendering is only visible in the popup if the following condition is met: `socket:typee~.+&socket:typee!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -687,7 +685,7 @@ This is rendered with `
European wall p This tagrendering is only visible in the popup if the following condition is met: `socket:typee~.+&socket:typee!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -710,7 +708,7 @@ This is rendered with `
Chademo This tagrendering is only visible in the popup if the following condition is met: `socket:chademo~.+&socket:chademo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -733,7 +731,7 @@ This is rendered with `
Chademo This tagrendering is only visible in the popup if the following condition is met: `socket:chademo~.+&socket:chademo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -756,7 +754,7 @@ This is rendered with `
Chademo This tagrendering is only visible in the popup if the following condition is met: `socket:chademo~.+&socket:chademo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -780,7 +778,7 @@ This is rendered with `
Type 1 with cab This tagrendering is only visible in the popup if the following condition is met: `socket:type1_cable~.+&socket:type1_cable!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -803,7 +801,7 @@ This is rendered with `
Type 1 with cab This tagrendering is only visible in the popup if the following condition is met: `socket:type1_cable~.+&socket:type1_cable!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -827,7 +825,7 @@ This is rendered with `
Type 1 with cab This tagrendering is only visible in the popup if the following condition is met: `socket:type1_cable~.+&socket:type1_cable!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -851,7 +849,7 @@ This is rendered with `
Type 1 witho This tagrendering is only visible in the popup if the following condition is met: `socket:type1~.+&socket:type1!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -874,7 +872,7 @@ This is rendered with `
Type 1 witho This tagrendering is only visible in the popup if the following condition is met: `socket:type1~.+&socket:type1!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -900,7 +898,7 @@ This is rendered with `
Type 1 witho This tagrendering is only visible in the popup if the following condition is met: `socket:type1~.+&socket:type1!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -924,7 +922,7 @@ This is rendered with `
Type 1 CCS This tagrendering is only visible in the popup if the following condition is met: `socket:type1_combo~.+&socket:type1_combo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -948,7 +946,7 @@ This is rendered with `
Type 1 CCS This tagrendering is only visible in the popup if the following condition is met: `socket:type1_combo~.+&socket:type1_combo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -974,7 +972,7 @@ This is rendered with `
Type 1 CCS This tagrendering is only visible in the popup if the following condition is met: `socket:type1_combo~.+&socket:type1_combo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -997,7 +995,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_supercharger~.+&socket:tesla_supercharger!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1021,7 +1019,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_supercharger~.+&socket:tesla_supercharger!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1046,7 +1044,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_supercharger~.+&socket:tesla_supercharger!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1070,7 +1068,7 @@ This is rendered with `
Type 2 (men This tagrendering is only visible in the popup if the following condition is met: `socket:type2~.+&socket:type2!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1094,7 +1092,7 @@ This is rendered with `
Type 2 (men This tagrendering is only visible in the popup if the following condition is met: `socket:type2~.+&socket:type2!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1118,7 +1116,7 @@ This is rendered with `
Type 2 (men This tagrendering is only visible in the popup if the following condition is met: `socket:type2~.+&socket:type2!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1142,7 +1140,7 @@ This is rendered with `
Type 2 CCS This tagrendering is only visible in the popup if the following condition is met: `socket:type2_combo~.+&socket:type2_combo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1166,7 +1164,7 @@ This is rendered with `
Type 2 CCS This tagrendering is only visible in the popup if the following condition is met: `socket:type2_combo~.+&socket:type2_combo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1189,7 +1187,7 @@ This is rendered with `
Type 2 CCS This tagrendering is only visible in the popup if the following condition is met: `socket:type2_combo~.+&socket:type2_combo!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1213,7 +1211,7 @@ This is rendered with `
Type 2 with cab This tagrendering is only visible in the popup if the following condition is met: `socket:type2_cable~.+&socket:type2_cable!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1237,7 +1235,7 @@ This is rendered with `
Type 2 with cab This tagrendering is only visible in the popup if the following condition is met: `socket:type2_cable~.+&socket:type2_cable!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1261,7 +1259,7 @@ This is rendered with `
Type 2 with cab This tagrendering is only visible in the popup if the following condition is met: `socket:type2_cable~.+&socket:type2_cable!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1285,7 +1283,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_supercharger_ccs~.+&socket:tesla_supercharger_ccs!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1309,7 +1307,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_supercharger_ccs~.+&socket:tesla_supercharger_ccs!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1332,7 +1330,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_supercharger_ccs~.+&socket:tesla_supercharger_ccs!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1355,7 +1353,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_destination~.+&socket:tesla_destination!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1379,7 +1377,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_destination~.+&socket:tesla_destination!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1404,7 +1402,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_destination~.+&socket:tesla_destination!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1428,7 +1426,7 @@ This is rendered with `
Tesla superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_destination~.+&socket:tesla_destination!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1452,7 +1450,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_destination~.+&socket:tesla_destination!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1476,7 +1474,7 @@ This is rendered with `
Tesla Superchar This tagrendering is only visible in the popup if the following condition is met: `socket:tesla_destination~.+&socket:tesla_destination!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1499,7 +1497,7 @@ This is rendered with `
USB to char This tagrendering is only visible in the popup if the following condition is met: `socket:USB-A~.+&socket:USB-A!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1523,7 +1521,7 @@ This is rendered with `
USB to char This tagrendering is only visible in the popup if the following condition is met: `socket:USB-A~.+&socket:USB-A!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1547,7 +1545,7 @@ This is rendered with `
USB to char This tagrendering is only visible in the popup if the following condition is met: `socket:USB-A~.+&socket:USB-A!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1570,7 +1568,7 @@ This is rendered with `
Bosch Active Co This tagrendering is only visible in the popup if the following condition is met: `socket:bosch_3pin~.+&socket:bosch_3pin!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1593,7 +1591,7 @@ This is rendered with `
Bosch Active Co This tagrendering is only visible in the popup if the following condition is met: `socket:bosch_3pin~.+&socket:bosch_3pin!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1616,7 +1614,7 @@ This is rendered with `
Bosch Active Co This tagrendering is only visible in the popup if the following condition is met: `socket:bosch_3pin~.+&socket:bosch_3pin!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1639,7 +1637,7 @@ This is rendered with `
Bosch Active Co This tagrendering is only visible in the popup if the following condition is met: `socket:bosch_5pin~.+&socket:bosch_5pin!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1662,7 +1660,7 @@ This is rendered with `
Bosch Active Co This tagrendering is only visible in the popup if the following condition is met: `socket:bosch_5pin~.+&socket:bosch_5pin!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -1685,7 +1683,7 @@ This is rendered with `
Bosch Active Co This tagrendering is only visible in the popup if the following condition is met: `socket:bosch_5pin~.+&socket:bosch_5pin!=0` -This tagrendering is part of group `technical` +This tagrendering has labels `technical` @@ -2017,16 +2015,12 @@ This tagrendering has no question and is thus read-only -This tagrendering is part of group `technical` - -### questions +### leftover-questions -Show the images block at this location - This tagrendering has no question and is thus read-only @@ -2037,10 +2031,50 @@ This tagrendering has no question and is thus read-only -Shows a small map with the feature. Added by default to every popup +This tagrendering has no question and is thus read-only + + + + + +### move-button + + This tagrendering has no question and is thus read-only - + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/themes/cyclofix/cyclofix.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/cyclofix/cyclofix.json) diff --git a/Docs/Layers/climbing.md b/Docs/Layers/climbing.md index 6c85aa3dcd..6bb1c002b2 100644 --- a/Docs/Layers/climbing.md +++ b/Docs/Layers/climbing.md @@ -228,6 +228,38 @@ This is rendered with `A fee of {charge} should be paid for climbing here` - *Climbing here is free of charge* corresponds with `fee=no` - *Paying a fee is required to climb here* corresponds with `fee=yes` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/climbing/climbing.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/climbing/climbing.json) diff --git a/Docs/Layers/climbing_area.md b/Docs/Layers/climbing_area.md index b4d70aa7e3..27fb1c0f47 100644 --- a/Docs/Layers/climbing_area.md +++ b/Docs/Layers/climbing_area.md @@ -79,8 +79,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -237,6 +235,28 @@ The question is *Is bouldering possible here?* - *Bouldering is possible, allthough there are only a few routes* corresponds with `climbing:boulder=limited` - *There are {climbing:boulder} boulder routes* corresponds with `climbing:boulder~.+` - This option cannot be chosen as answer - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/climbing_area/climbing_area.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/climbing_area/climbing_area.json) diff --git a/Docs/Layers/climbing_club.md b/Docs/Layers/climbing_club.md index 72488f16ee..6dce71b1bf 100644 --- a/Docs/Layers/climbing_club.md +++ b/Docs/Layers/climbing_club.md @@ -153,6 +153,38 @@ This rendering asks information about the property [opening_hours](https://wiki This is rendered with `

Opening hours

{opening_hours_table(opening_hours)}` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/climbing_club/climbing_club.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/climbing_club/climbing_club.json) diff --git a/Docs/Layers/climbing_gym.md b/Docs/Layers/climbing_gym.md index 8b71bb04e0..ee22afa602 100644 --- a/Docs/Layers/climbing_gym.md +++ b/Docs/Layers/climbing_gym.md @@ -85,8 +85,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -362,6 +360,38 @@ The question is *Is there a speed climbing wall?* - *There is no speed climbing wall* corresponds with `climbing:speed=no` - *There are {climbing:speed} speed climbing walls* corresponds with `climbing:speed~.+` - This option cannot be chosen as answer - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/climbing_gym/climbing_gym.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/climbing_gym/climbing_gym.json) diff --git a/Docs/Layers/climbing_opportunity.md b/Docs/Layers/climbing_opportunity.md index e87ea6e918..c2bee4309b 100644 --- a/Docs/Layers/climbing_opportunity.md +++ b/Docs/Layers/climbing_opportunity.md @@ -81,6 +81,38 @@ The question is *Is climbing possible here?* - *Climbing is not possible here* corresponds with `climbing=no` - *Climbing is not possible here* corresponds with `sport!~^(climbing)$` - This option cannot be chosen as answer - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/climbing_opportunity/climbing_opportunity.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/climbing_opportunity/climbing_opportunity.json) diff --git a/Docs/Layers/climbing_route.md b/Docs/Layers/climbing_route.md index a453fbe35d..ea27ae2bb2 100644 --- a/Docs/Layers/climbing_route.md +++ b/Docs/Layers/climbing_route.md @@ -75,8 +75,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -173,6 +171,38 @@ This rendering asks information about the property [_embedding_features_with_ro This is rendered with `The rock type is {_embedding_features_with_rock:rock} as stated on the surrounding crag` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/climbing_route/climbing_route.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/climbing_route/climbing_route.json) diff --git a/Docs/Layers/clock.md b/Docs/Layers/clock.md index cb92006afb..f898bef49d 100644 --- a/Docs/Layers/clock.md +++ b/Docs/Layers/clock.md @@ -77,8 +77,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -227,6 +225,68 @@ This is rendered with `This clock has {faces} faces` - *This clock has one face* corresponds with `faces=1` - *This clock has two faces* corresponds with `faces=2` - *This clock has four faces* corresponds with `faces=4` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/clock/clock.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/clock/clock.json) diff --git a/Docs/Layers/crab_address.md b/Docs/Layers/crab_address.md index b4cd9800ae..d1429e744a 100644 --- a/Docs/Layers/crab_address.md +++ b/Docs/Layers/crab_address.md @@ -49,6 +49,38 @@ Elements must have the all of following tags to be shown on this layer: This tagrendering has no question and is thus read-only - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/crab_address/crab_address.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/crab_address/crab_address.json) diff --git a/Docs/Layers/crossings.md b/Docs/Layers/crossings.md index 400bef8224..80eb9a2d0d 100644 --- a/Docs/Layers/crossings.md +++ b/Docs/Layers/crossings.md @@ -84,8 +84,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -317,6 +315,38 @@ This tagrendering is only visible in the popup if the following condition is met +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/cultural_places_without_etymology.md b/Docs/Layers/cultural_places_without_etymology.md index 594b792c85..5ec96a4565 100644 --- a/Docs/Layers/cultural_places_without_etymology.md +++ b/Docs/Layers/cultural_places_without_etymology.md @@ -169,6 +169,18 @@ This tagrendering has no question and is thus read-only -This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` +This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/themes/etymology/etymology.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/etymology/etymology.json) diff --git a/Docs/Layers/cycleways_and_roads.md b/Docs/Layers/cycleways_and_roads.md index 5544f8018a..8178273de9 100644 --- a/Docs/Layers/cycleways_and_roads.md +++ b/Docs/Layers/cycleways_and_roads.md @@ -419,6 +419,48 @@ The question is *How is this cycleway separated from the road?* - *This cycleway is separated by a kerb* corresponds with `separation=kerb` -This tagrendering is only visible in the popup if the following condition is met: `highway=cycleway|highway=path` +This tagrendering is only visible in the popup if the following condition is met: `highway=cycleway|highway=path` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### split-button + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/cycleways_and_roads/cycleways_and_roads.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/cycleways_and_roads/cycleways_and_roads.json) diff --git a/Docs/Layers/defibrillator.md b/Docs/Layers/defibrillator.md index 60e7a2e08e..d87b6e4e5e 100644 --- a/Docs/Layers/defibrillator.md +++ b/Docs/Layers/defibrillator.md @@ -84,8 +84,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -344,6 +342,68 @@ This is rendered with `Extra information for OpenStreetMap experts: {fixme}` +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/dentist.md b/Docs/Layers/dentist.md index 8eb382ff23..20281d7ff8 100644 --- a/Docs/Layers/dentist.md +++ b/Docs/Layers/dentist.md @@ -73,8 +73,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -169,6 +167,68 @@ This is rendered with `This dentist is called {name}` +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/direction.md b/Docs/Layers/direction.md index 9b419f3a81..90d44676f4 100644 --- a/Docs/Layers/direction.md +++ b/Docs/Layers/direction.md @@ -1,4 +1,4 @@ - +[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources) direction =========== @@ -15,7 +15,7 @@ This layer visualizes directions - This layer is shown at zoomlevel **16** and higher - - This layer cannot be toggled in the filter view. If you import this layer in your theme, override `title` to make this toggleable. + - Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable. @@ -41,7 +41,7 @@ Elements must have the all of following tags to be shown on this layer: - - camera:direction~^..*$|direction~^..*$ + - camera:direction~.+|direction~.+ [Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B%22camera%3Adirection%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B%22direction%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) @@ -51,6 +51,38 @@ Elements must have the all of following tags to be shown on this layer: Supported attributes ---------------------- - -This document is autogenerated from [assets/layers/direction/direction.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/direction/direction.json) \ No newline at end of file + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + +This document is autogenerated from [assets/layers/direction/direction.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/direction/direction.json) diff --git a/Docs/Layers/doctors.md b/Docs/Layers/doctors.md index ec2e6148d4..0ea9db7285 100644 --- a/Docs/Layers/doctors.md +++ b/Docs/Layers/doctors.md @@ -75,8 +75,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -200,6 +198,68 @@ This is rendered with `This doctor is specialized in {healthcare:speciality}` +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/dogfoodb.md b/Docs/Layers/dogfoodb.md index d897c28947..15ef27c2a7 100644 --- a/Docs/Layers/dogfoodb.md +++ b/Docs/Layers/dogfoodb.md @@ -94,8 +94,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -618,20 +616,16 @@ This tagrendering is only visible in the popup if the following condition is met -Shows the reviews module (including the possibility to leave a review) - This tagrendering has no question and is thus read-only -### questions +### leftover-questions -Show the images block at this location - This tagrendering has no question and is thus read-only @@ -642,7 +636,15 @@ This tagrendering has no question and is thus read-only -Shows a small map with the feature. Added by default to every popup +This tagrendering has no question and is thus read-only + + + + + +### move-button + + This tagrendering has no question and is thus read-only @@ -650,6 +652,38 @@ This tagrendering has no question and is thus read-only +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/dogpark.md b/Docs/Layers/dogpark.md index 926785ecd3..8249ae3a03 100644 --- a/Docs/Layers/dogpark.md +++ b/Docs/Layers/dogpark.md @@ -127,8 +127,6 @@ This tagrendering has no question and is thus read-only -Shows the reviews module (including the possibility to leave a review) - This tagrendering has no question and is thus read-only @@ -139,10 +137,40 @@ This tagrendering has no question and is thus read-only -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + This tagrendering has no question and is thus read-only - + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/dogpark/dogpark.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/dogpark/dogpark.json) diff --git a/Docs/Layers/dogshop.md b/Docs/Layers/dogshop.md index 221ce62e6e..ea510389e2 100644 --- a/Docs/Layers/dogshop.md +++ b/Docs/Layers/dogshop.md @@ -79,8 +79,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -515,20 +513,6 @@ This tagrendering has no question and is thus read-only -Shows the reviews module (including the possibility to leave a review) - -This tagrendering has no question and is thus read-only - - - - - -### questions - - - -Show the images block at this location - This tagrendering has no question and is thus read-only @@ -539,7 +523,15 @@ This tagrendering has no question and is thus read-only -Shows a small map with the feature. Added by default to every popup +This tagrendering has no question and is thus read-only + + + + + +### move-button + + This tagrendering has no question and is thus read-only @@ -547,6 +539,38 @@ This tagrendering has no question and is thus read-only +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/drinking_water.md b/Docs/Layers/drinking_water.md index f2e4121ac0..d73207c5bd 100644 --- a/Docs/Layers/drinking_water.md +++ b/Docs/Layers/drinking_water.md @@ -77,8 +77,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -130,6 +128,68 @@ This tagrendering has no question and is thus read-only -This tagrendering is only visible in the popup if the following condition is met: `_closest_other_drinking_water_id~.+` +This tagrendering is only visible in the popup if the following condition is met: `_closest_other_drinking_water_id~.+` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/drinking_water/drinking_water.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/drinking_water/drinking_water.json) diff --git a/Docs/Layers/dumpstations.md b/Docs/Layers/dumpstations.md index 119296efe4..6b1e3cedd7 100644 --- a/Docs/Layers/dumpstations.md +++ b/Docs/Layers/dumpstations.md @@ -77,8 +77,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -198,6 +196,38 @@ This is rendered with `This station is part of network {network}` +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + ### operator @@ -224,30 +254,6 @@ The question is *Does this place have a power supply?* - *This place has a power supply* corresponds with `power_supply=yes` - *This place does not have power supply* corresponds with `power_supply=no` - - - - -### questions - - - -Show the images block at this location - -This tagrendering has no question and is thus read-only - - - - - -### minimap - - - -Shows a small map with the feature. Added by default to every popup - -This tagrendering has no question and is thus read-only - This document is autogenerated from [assets/themes/campersite/campersite.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/campersite/campersite.json) diff --git a/Docs/Layers/education_institutions_without_etymology.md b/Docs/Layers/education_institutions_without_etymology.md index a4c84324cb..27861f66aa 100644 --- a/Docs/Layers/education_institutions_without_etymology.md +++ b/Docs/Layers/education_institutions_without_etymology.md @@ -169,6 +169,18 @@ This tagrendering has no question and is thus read-only -This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` +This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/themes/etymology/etymology.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/etymology/etymology.json) diff --git a/Docs/Layers/elevator.md b/Docs/Layers/elevator.md index 0d90eac285..2442dd21ae 100644 --- a/Docs/Layers/elevator.md +++ b/Docs/Layers/elevator.md @@ -76,8 +76,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -174,8 +172,6 @@ This is rendered with `This elevator has a depth of {canonical(elevator:depth)} -An accessibility feature: induction loops are for hard-hearing persons which have an FM-receiver. - The question is *Does this place have an audio induction loop for people with reduced hearing?* @@ -194,6 +190,38 @@ The question is *Does this place have an audio induction loop for people with r This tagrendering has no question and is thus read-only - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/elevator/elevator.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/elevator/elevator.json) diff --git a/Docs/Layers/entrance.md b/Docs/Layers/entrance.md index 3e2d67c487..7dbecc8f05 100644 --- a/Docs/Layers/entrance.md +++ b/Docs/Layers/entrance.md @@ -79,8 +79,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -210,6 +208,38 @@ This is rendered with `The kerb height of this door is {kerb:height}` - *This door does not have a kerb* corresponds with `kerb:height=0` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/entrance/entrance.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/entrance/entrance.json) diff --git a/Docs/Layers/etymology.md b/Docs/Layers/etymology.md index b1c6c30788..2a4abba9ad 100644 --- a/Docs/Layers/etymology.md +++ b/Docs/Layers/etymology.md @@ -169,6 +169,18 @@ This tagrendering has no question and is thus read-only -This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` +This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/etymology/etymology.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/etymology/etymology.json) diff --git a/Docs/Layers/extinguisher.md b/Docs/Layers/extinguisher.md index 1b77936a71..4cb4dac855 100644 --- a/Docs/Layers/extinguisher.md +++ b/Docs/Layers/extinguisher.md @@ -89,10 +89,40 @@ This is rendered with `Location: {location}` -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + This tagrendering has no question and is thus read-only - + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/extinguisher/extinguisher.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/extinguisher/extinguisher.json) diff --git a/Docs/Layers/facadegardens.md b/Docs/Layers/facadegardens.md index 574d446b02..ee114c251b 100644 --- a/Docs/Layers/facadegardens.md +++ b/Docs/Layers/facadegardens.md @@ -75,8 +75,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -192,12 +190,10 @@ This is rendered with `More details: {description}` -### questions +### leftover-questions -Show the images block at this location - This tagrendering has no question and is thus read-only @@ -208,10 +204,50 @@ This tagrendering has no question and is thus read-only -Shows a small map with the feature. Added by default to every popup +This tagrendering has no question and is thus read-only + + + + + +### move-button + + This tagrendering has no question and is thus read-only - + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/themes/facadegardens/facadegardens.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/facadegardens/facadegardens.json) diff --git a/Docs/Layers/fietsstraat.md b/Docs/Layers/fietsstraat.md index b540f4b067..b92032b5dc 100644 --- a/Docs/Layers/fietsstraat.md +++ b/Docs/Layers/fietsstraat.md @@ -69,7 +69,15 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + This tagrendering has no question and is thus read-only @@ -77,6 +85,28 @@ This tagrendering has no question and is thus read-only +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + ### is_cyclestreet @@ -136,24 +166,10 @@ This tagrendering is only visible in the popup if the following condition is met -### questions +### split-button -Show the images block at this location - -This tagrendering has no question and is thus read-only - - - - - -### minimap - - - -Shows a small map with the feature. Added by default to every popup - This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/fire_station.md b/Docs/Layers/fire_station.md index e17573f0e2..7002ba80a6 100644 --- a/Docs/Layers/fire_station.md +++ b/Docs/Layers/fire_station.md @@ -156,10 +156,40 @@ This is rendered with `The operator is a(n) {operator:type} entity.` -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + This tagrendering has no question and is thus read-only - + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/fire_station/fire_station.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/fire_station/fire_station.json) diff --git a/Docs/Layers/fitness_centre.md b/Docs/Layers/fitness_centre.md index efa8df0203..77a5906d1d 100644 --- a/Docs/Layers/fitness_centre.md +++ b/Docs/Layers/fitness_centre.md @@ -94,8 +94,6 @@ This is rendered with `This fitness centre is called {name}` -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -223,7 +221,15 @@ This is rendered with `Located on the {level}th floor` -Shows the reviews module (including the possibility to leave a review) +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + This tagrendering has no question and is thus read-only @@ -231,6 +237,28 @@ This tagrendering has no question and is thus read-only +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/fitness_station.md b/Docs/Layers/fitness_station.md index 9be0aaf081..c1306ab897 100644 --- a/Docs/Layers/fitness_station.md +++ b/Docs/Layers/fitness_station.md @@ -72,8 +72,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -166,6 +164,38 @@ This is rendered with `

Opening hours

{opening_hours_table(opening_hours +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/food.md b/Docs/Layers/food.md index 3e3726ada8..0d1b8d612c 100644 --- a/Docs/Layers/food.md +++ b/Docs/Layers/food.md @@ -97,8 +97,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -621,7 +619,15 @@ This tagrendering is only visible in the popup if the following condition is met -Shows the reviews module (including the possibility to leave a review) +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + This tagrendering has no question and is thus read-only @@ -629,6 +635,58 @@ This tagrendering has no question and is thus read-only +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/friture.md b/Docs/Layers/friture.md index 8b463a582b..4660cad0e9 100644 --- a/Docs/Layers/friture.md +++ b/Docs/Layers/friture.md @@ -94,8 +94,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -618,20 +616,16 @@ This tagrendering is only visible in the popup if the following condition is met -Shows the reviews module (including the possibility to leave a review) - This tagrendering has no question and is thus read-only -### questions +### leftover-questions -Show the images block at this location - This tagrendering has no question and is thus read-only @@ -642,7 +636,15 @@ This tagrendering has no question and is thus read-only -Shows a small map with the feature. Added by default to every popup +This tagrendering has no question and is thus read-only + + + + + +### move-button + + This tagrendering has no question and is thus read-only @@ -650,6 +652,38 @@ This tagrendering has no question and is thus read-only +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/ghost_bike.md b/Docs/Layers/ghost_bike.md index db5d1794c6..deb3e0efc8 100644 --- a/Docs/Layers/ghost_bike.md +++ b/Docs/Layers/ghost_bike.md @@ -82,8 +82,6 @@ This tagrendering has no question and is thus read-only -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -147,6 +145,68 @@ This rendering asks information about the property [start_date](https://wiki.op This is rendered with `Placed on {start_date}` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/ghost_bike/ghost_bike.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/ghost_bike/ghost_bike.json) diff --git a/Docs/Layers/governments.md b/Docs/Layers/governments.md index 75bc5fd09b..102b8f8fa8 100644 --- a/Docs/Layers/governments.md +++ b/Docs/Layers/governments.md @@ -72,8 +72,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -150,6 +148,38 @@ This rendering asks information about the property [name](https://wiki.openstre This is rendered with `This Governmental Office is called {name}` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/governments/governments.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/governments/governments.json) diff --git a/Docs/Layers/hackerspace.md b/Docs/Layers/hackerspace.md index 1e84616fcb..a39bb95371 100644 --- a/Docs/Layers/hackerspace.md +++ b/Docs/Layers/hackerspace.md @@ -80,8 +80,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -249,8 +247,6 @@ The question is *Is a CNC drill available at this hackerspace?* -Shows the reviews module (including the possibility to leave a review) - This tagrendering has no question and is thus read-only @@ -301,6 +297,38 @@ This rendering asks information about the property [start_date](https://wiki.op This is rendered with `This hackerspace was founded at {start_date}` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/hackerspace/hackerspace.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/hackerspace/hackerspace.json) diff --git a/Docs/Layers/health_and_social_places_without_etymology.md b/Docs/Layers/health_and_social_places_without_etymology.md index fad8bc4ea2..1768354427 100644 --- a/Docs/Layers/health_and_social_places_without_etymology.md +++ b/Docs/Layers/health_and_social_places_without_etymology.md @@ -169,6 +169,18 @@ This tagrendering has no question and is thus read-only -This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` +This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/themes/etymology/etymology.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/etymology/etymology.json) diff --git a/Docs/Layers/hospital.md b/Docs/Layers/hospital.md index a8c8829d06..4ce236216e 100644 --- a/Docs/Layers/hospital.md +++ b/Docs/Layers/hospital.md @@ -156,6 +156,38 @@ This is rendered with `{contact:website}* corresponds with `contact:website~.+` - This option cannot be chosen as answer - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/hospital/hospital.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/hospital/hospital.json) diff --git a/Docs/Layers/hotel.md b/Docs/Layers/hotel.md index 40d1da20a0..0f3d68e570 100644 --- a/Docs/Layers/hotel.md +++ b/Docs/Layers/hotel.md @@ -77,8 +77,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -89,8 +87,6 @@ This tagrendering has no question and is thus read-only -Shows the reviews module (including the possibility to leave a review) - This tagrendering has no question and is thus read-only @@ -245,6 +241,48 @@ This is rendered with `The network name is {internet_access:ssid}` - *Telekom* corresponds with `internet_access:ssid=Telekom` -This tagrendering is only visible in the popup if the following condition is met: `internet_access=wlan` +This tagrendering is only visible in the popup if the following condition is met: `internet_access=wlan` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/hotel/hotel.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/hotel/hotel.json) diff --git a/Docs/Layers/hydrant.md b/Docs/Layers/hydrant.md index 2429fda6cd..a7e078caa8 100644 --- a/Docs/Layers/hydrant.md +++ b/Docs/Layers/hydrant.md @@ -197,10 +197,40 @@ This is rendered with `Coupling diameters: {couplings:diameters}` -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + This tagrendering has no question and is thus read-only - + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/hydrant/hydrant.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/hydrant/hydrant.json) diff --git a/Docs/Layers/indoors.md b/Docs/Layers/indoors.md index 8d245d954b..9e58583974 100644 --- a/Docs/Layers/indoors.md +++ b/Docs/Layers/indoors.md @@ -72,8 +72,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -108,6 +106,38 @@ This is rendered with `This room is named {name}` -This tagrendering is only visible in the popup if the following condition is met: `indoor=room|indoor=area|indoor=corridor` +This tagrendering is only visible in the popup if the following condition is met: `indoor=room|indoor=area|indoor=corridor` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/indoors/indoors.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/indoors/indoors.json) diff --git a/Docs/Layers/information_board.md b/Docs/Layers/information_board.md index 181405c9fc..b4dad3c737 100644 --- a/Docs/Layers/information_board.md +++ b/Docs/Layers/information_board.md @@ -58,10 +58,70 @@ Elements must have the all of following tags to be shown on this layer: -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + This tagrendering has no question and is thus read-only - + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/information_board/information_board.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/information_board/information_board.json) diff --git a/Docs/Layers/kerbs.md b/Docs/Layers/kerbs.md index 5451413447..24061f66e7 100644 --- a/Docs/Layers/kerbs.md +++ b/Docs/Layers/kerbs.md @@ -76,8 +76,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -146,6 +144,38 @@ This is rendered with `Kerb height: {kerb:height}` +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/kindergarten_childcare.md b/Docs/Layers/kindergarten_childcare.md index 4a98883bce..22ee195cd7 100644 --- a/Docs/Layers/kindergarten_childcare.md +++ b/Docs/Layers/kindergarten_childcare.md @@ -187,6 +187,68 @@ This rendering asks information about the property [capacity](https://wiki.open This is rendered with `This facility has room for {capacity} kids` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/kindergarten_childcare/kindergarten_childcare.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/kindergarten_childcare/kindergarten_childcare.json) diff --git a/Docs/Layers/lit_streets.md b/Docs/Layers/lit_streets.md index f7884cfd9e..ce4cee79e6 100644 --- a/Docs/Layers/lit_streets.md +++ b/Docs/Layers/lit_streets.md @@ -70,8 +70,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -97,12 +95,10 @@ The question is *Is this street lit?* -### questions +### leftover-questions -Show the images block at this location - This tagrendering has no question and is thus read-only @@ -113,10 +109,30 @@ This tagrendering has no question and is thus read-only -Shows a small map with the feature. Added by default to every popup +This tagrendering has no question and is thus read-only + + + + + +### split-button + + This tagrendering has no question and is thus read-only - + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/themes/street_lighting/street_lighting.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/street_lighting/street_lighting.json) diff --git a/Docs/Layers/map.md b/Docs/Layers/map.md index 0b61b34b26..47bbffc597 100644 --- a/Docs/Layers/map.md +++ b/Docs/Layers/map.md @@ -71,8 +71,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -120,6 +118,68 @@ The question is *Is the OpenStreetMap-attribution given?* This tagrendering is only visible in the popup if the following condition is met: `map_source~^((O|)pen(S|s)treet(M|m)ap)$|map_source=osm|map_source=OSM` -This tagrendering has labels `map` +This tagrendering has labels `map` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/map/map.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/map/map.json) diff --git a/Docs/Layers/maproulette.md b/Docs/Layers/maproulette.md index 1e41542b5f..3075277ab6 100644 --- a/Docs/Layers/maproulette.md +++ b/Docs/Layers/maproulette.md @@ -106,6 +106,38 @@ This tagrendering has no question and is thus read-only +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/maproulette_challenge.md b/Docs/Layers/maproulette_challenge.md index 0d8929b22c..64589618fd 100644 --- a/Docs/Layers/maproulette_challenge.md +++ b/Docs/Layers/maproulette_challenge.md @@ -99,6 +99,38 @@ This tagrendering has no question and is thus read-only +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/maxspeed.md b/Docs/Layers/maxspeed.md index 8ec1b834c8..3f8be9cb7b 100644 --- a/Docs/Layers/maxspeed.md +++ b/Docs/Layers/maxspeed.md @@ -85,6 +85,48 @@ This is rendered with `The maximum allowed speed on this road is {canonical(max - *This is a living street, which has a maxspeed of 20km/h* corresponds with `highway=living_street&_country!=be` - This option cannot be chosen as answer - *This is a living street, which has a maxspeed of 20km/h* corresponds with `highway=living_street` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### split-button + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/maxspeed/maxspeed.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/maxspeed/maxspeed.json) diff --git a/Docs/Layers/medical-shops.md b/Docs/Layers/medical-shops.md index 60ac0f13e8..e3b26dbcfb 100644 --- a/Docs/Layers/medical-shops.md +++ b/Docs/Layers/medical-shops.md @@ -79,8 +79,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -515,20 +513,6 @@ This tagrendering has no question and is thus read-only -Shows the reviews module (including the possibility to leave a review) - -This tagrendering has no question and is thus read-only - - - - - -### questions - - - -Show the images block at this location - This tagrendering has no question and is thus read-only @@ -539,7 +523,15 @@ This tagrendering has no question and is thus read-only -Shows a small map with the feature. Added by default to every popup +This tagrendering has no question and is thus read-only + + + + + +### move-button + + This tagrendering has no question and is thus read-only @@ -547,6 +539,38 @@ This tagrendering has no question and is thus read-only +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/nature_reserve.md b/Docs/Layers/nature_reserve.md index 7875de6dd8..96ad7decc1 100644 --- a/Docs/Layers/nature_reserve.md +++ b/Docs/Layers/nature_reserve.md @@ -79,8 +79,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -275,8 +273,6 @@ This tagrendering has no question and is thus read-only -Shows a wikipedia box with the corresponding wikipedia article; the wikidata-item link can be changed by a contributor - The question is *What is the corresponding Wikidata entity?* This rendering asks information about the property [wikidata](https://wiki.openstreetmap.org/wiki/Key:wikidata) @@ -295,6 +291,38 @@ This is rendered with `{wikipedia():max-height:25rem}` +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/observation_tower.md b/Docs/Layers/observation_tower.md index 83aa52fd6d..b99b86be4f 100644 --- a/Docs/Layers/observation_tower.md +++ b/Docs/Layers/observation_tower.md @@ -78,8 +78,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -268,8 +266,6 @@ This tagrendering is only visible in the popup if the following condition is met -Shows a wikipedia box with the corresponding wikipedia article; the wikidata-item link can be changed by a contributor - The question is *What is the corresponding Wikidata entity?* This rendering asks information about the property [wikidata](https://wiki.openstreetmap.org/wiki/Key:wikidata) @@ -284,6 +280,48 @@ This is rendered with `{wikipedia():max-height:25rem}` - This option cannot be chosen as answer - *No Wikipedia page has been linked yet* corresponds with `` - This option cannot be chosen as answer - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/observation_tower/observation_tower.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/observation_tower/observation_tower.json) diff --git a/Docs/Layers/osm_community_index.md b/Docs/Layers/osm_community_index.md index 96d27a0258..c866f7946c 100644 --- a/Docs/Layers/osm_community_index.md +++ b/Docs/Layers/osm_community_index.md @@ -83,6 +83,38 @@ This tagrendering is only visible in the popup if the following condition is met +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/parcel_lockers.md b/Docs/Layers/parcel_lockers.md index d6e5c3ec4c..3aabb5f529 100644 --- a/Docs/Layers/parcel_lockers.md +++ b/Docs/Layers/parcel_lockers.md @@ -74,8 +74,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -187,6 +185,68 @@ This tagrendering is only visible in the popup if the following condition is met +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/parking.md b/Docs/Layers/parking.md index 8f1ca4fda8..6c8a6b60e4 100644 --- a/Docs/Layers/parking.md +++ b/Docs/Layers/parking.md @@ -74,8 +74,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -164,6 +162,68 @@ This rendering asks information about the property [capacity](https://wiki.open This is rendered with `There are {capacity} parking spots` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/parking/parking.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/parking/parking.json) diff --git a/Docs/Layers/parking_spaces.md b/Docs/Layers/parking_spaces.md index 368ea9d986..88bf5a2342 100644 --- a/Docs/Layers/parking_spaces.md +++ b/Docs/Layers/parking_spaces.md @@ -71,8 +71,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -120,6 +118,38 @@ This tagrendering has no question and is thus read-only - *This parking space has 1 space.* corresponds with `capacity=1` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/parking_spaces/parking_spaces.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/parking_spaces/parking_spaces.json) diff --git a/Docs/Layers/parking_ticket_machine.md b/Docs/Layers/parking_ticket_machine.md index 5d3d3cf938..bd6fee7cda 100644 --- a/Docs/Layers/parking_ticket_machine.md +++ b/Docs/Layers/parking_ticket_machine.md @@ -72,8 +72,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -170,6 +168,38 @@ This is rendered with `This parking ticket machine has the reference number {re - *This parking ticket machine has no reference number* corresponds with `noref=yes` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/parking_ticket_machine/parking_ticket_machine.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/parking_ticket_machine/parking_ticket_machine.json) diff --git a/Docs/Layers/parks_and_forests_without_etymology.md b/Docs/Layers/parks_and_forests_without_etymology.md index 228a8c9ddd..f00949229b 100644 --- a/Docs/Layers/parks_and_forests_without_etymology.md +++ b/Docs/Layers/parks_and_forests_without_etymology.md @@ -169,6 +169,18 @@ This tagrendering has no question and is thus read-only -This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` +This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/themes/etymology/etymology.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/etymology/etymology.json) diff --git a/Docs/Layers/pharmacy.md b/Docs/Layers/pharmacy.md index 74c8939851..6a6dfa51a6 100644 --- a/Docs/Layers/pharmacy.md +++ b/Docs/Layers/pharmacy.md @@ -76,8 +76,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -189,6 +187,68 @@ The question is *Is this pharmacy easy to access on a wheelchair?* +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/physiotherapist.md b/Docs/Layers/physiotherapist.md index dbd2ee02d0..83cf6b96a4 100644 --- a/Docs/Layers/physiotherapist.md +++ b/Docs/Layers/physiotherapist.md @@ -73,8 +73,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -176,6 +174,68 @@ This is rendered with `the web +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/railway_platforms.md b/Docs/Layers/railway_platforms.md index 8a7ff8179c..8fc5bd4727 100644 --- a/Docs/Layers/railway_platforms.md +++ b/Docs/Layers/railway_platforms.md @@ -89,6 +89,38 @@ This is rendered with `Located on the {level}th floor` - This option cannot be chosen as answer - *Located on the first floor* corresponds with `level=1` - *Located on the first basement level* corresponds with `level=-1` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/railway_platforms/railway_platforms.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/railway_platforms/railway_platforms.json) diff --git a/Docs/Layers/rainbow_crossing_high_zoom.md b/Docs/Layers/rainbow_crossing_high_zoom.md index dcd1e19bf5..cd2a58985b 100644 --- a/Docs/Layers/rainbow_crossing_high_zoom.md +++ b/Docs/Layers/rainbow_crossing_high_zoom.md @@ -59,8 +59,6 @@ Elements must have the all of following tags to be shown on this layer: -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -87,12 +85,10 @@ This tagrendering is only visible in the popup if the following condition is met -### questions +### leftover-questions -Show the images block at this location - This tagrendering has no question and is thus read-only @@ -103,10 +99,20 @@ This tagrendering has no question and is thus read-only -Shows a small map with the feature. Added by default to every popup +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + This tagrendering has no question and is thus read-only - + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/themes/rainbow_crossings/rainbow_crossings.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/rainbow_crossings/rainbow_crossings.json) diff --git a/Docs/Layers/rainbow_crossings.md b/Docs/Layers/rainbow_crossings.md index 37cac2f73b..dbbfb6e4ba 100644 --- a/Docs/Layers/rainbow_crossings.md +++ b/Docs/Layers/rainbow_crossings.md @@ -59,8 +59,6 @@ Elements must have the all of following tags to be shown on this layer: -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -83,6 +81,38 @@ The question is *Does this crossing has rainbow paintings?* - This option cannot be chosen as answer -This tagrendering is only visible in the popup if the following condition is met: `highway=crossing` +This tagrendering is only visible in the popup if the following condition is met: `highway=crossing` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/rainbow_crossings/rainbow_crossings.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/rainbow_crossings/rainbow_crossings.json) diff --git a/Docs/Layers/reception_desk.md b/Docs/Layers/reception_desk.md index 625bb3928a..a847ab9057 100644 --- a/Docs/Layers/reception_desk.md +++ b/Docs/Layers/reception_desk.md @@ -71,8 +71,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -122,8 +120,6 @@ This is rendered with `The height of the desk is {canonical(desk:height)}does not
have an audio induction loop* corresponds with `hearing_loop=no` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/reception_desk/reception_desk.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/reception_desk/reception_desk.json) diff --git a/Docs/Layers/recycling.md b/Docs/Layers/recycling.md index ce2ca6b7e7..cab9b28ae7 100644 --- a/Docs/Layers/recycling.md +++ b/Docs/Layers/recycling.md @@ -76,8 +76,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -186,18 +184,18 @@ The question is *What can be recycled here?* - Unselecting this answer will add - *Plastic can be recycled here* corresponds with `recycling:plastic=yes` - Unselecting this answer will add - - *Scrap metal can be recycled here* corresponds with `recycling:printer_cartridges=yes` + - *Printer cartridges can be recycled here* corresponds with `recycling:printer_cartridges=yes` - Unselecting this answer will add - - *Shoes can be recycled here* corresponds with `recycling:scrap_metal=yes` + - *Scrap metal can be recycled here* corresponds with `recycling:scrap_metal=yes` - Unselecting this answer will add - - *Small electrical appliances can be recycled here* corresponds with `recycling:shoes=yes` + - *Shoes can be recycled here* corresponds with `recycling:shoes=yes` - Unselecting this answer will add - *Small electrical appliances can be recycled here* corresponds with `recycling:small_appliances=yes` - Unselecting this answer will add - - *Needles can be recycled here* corresponds with `recycling:small_electrical_appliances=yes` + - *Small electrical appliances can be recycled here* corresponds with `recycling:small_electrical_appliances=yes` - This option cannot be chosen as answer - Unselecting this answer will add - - *Residual waste can be recycled here* corresponds with `recycling:needles=yes` + - *Needles can be recycled here* corresponds with `recycling:needles=yes` - Unselecting this answer will add - *Residual waste can be recycled here* corresponds with `recycling:waste=yes` - Unselecting this answer will add @@ -325,6 +323,68 @@ This is rendered with `This recycling facility can be used by {access}` +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/school.md b/Docs/Layers/school.md index 8379646344..cd00c105cf 100644 --- a/Docs/Layers/school.md +++ b/Docs/Layers/school.md @@ -239,6 +239,38 @@ This is rendered with `{email}` This tagrendering has no question and is thus read-only - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/school/school.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/school/school.json) diff --git a/Docs/Layers/shelter.md b/Docs/Layers/shelter.md index 5137638dea..70324bfc3a 100644 --- a/Docs/Layers/shelter.md +++ b/Docs/Layers/shelter.md @@ -86,6 +86,38 @@ This is rendered with `Shelter type: {shelter_type}` - *This is a shed with 3 walls, primarily intended for camping.* corresponds with `shelter_type=lean_to` - *This is a pavilion* corresponds with `shelter_type=pavilion` - *This is a basic hut, providing basic shelter and sleeping facilities.* corresponds with `shelter_type=basic_hut` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/shelter/shelter.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/shelter/shelter.json) diff --git a/Docs/Layers/shops.md b/Docs/Layers/shops.md index c7339e4054..0a364c8a9d 100644 --- a/Docs/Layers/shops.md +++ b/Docs/Layers/shops.md @@ -82,8 +82,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -518,7 +516,15 @@ This tagrendering has no question and is thus read-only -Shows the reviews module (including the possibility to leave a review) +This tagrendering has no question and is thus read-only + + + + + +### minimap + + This tagrendering has no question and is thus read-only @@ -526,6 +532,48 @@ This tagrendering has no question and is thus read-only +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/slow_roads.md b/Docs/Layers/slow_roads.md index a926dc6f33..aeb0f97ab7 100644 --- a/Docs/Layers/slow_roads.md +++ b/Docs/Layers/slow_roads.md @@ -61,8 +61,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -129,6 +127,38 @@ The question is *Is deze weg 's nachts verlicht?* - *'s nachts verlicht* corresponds with `lit=yes` - *Niet verlicht* corresponds with `lit=no` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/slow_roads/slow_roads.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/slow_roads/slow_roads.json) diff --git a/Docs/Layers/speed_camera.md b/Docs/Layers/speed_camera.md index 8d0e617b7b..3aaa85fe11 100644 --- a/Docs/Layers/speed_camera.md +++ b/Docs/Layers/speed_camera.md @@ -88,6 +88,38 @@ This tagrendering has no question and is thus read-only -This tagrendering is only visible in the popup if the following condition is met: `ref~.+` +This tagrendering is only visible in the popup if the following condition is met: `ref~.+` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/speed_camera/speed_camera.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/speed_camera/speed_camera.json) diff --git a/Docs/Layers/speed_display.md b/Docs/Layers/speed_display.md index f453a3e5de..49973e9b8f 100644 --- a/Docs/Layers/speed_display.md +++ b/Docs/Layers/speed_display.md @@ -90,6 +90,38 @@ This rendering asks information about the property [inscription](https://wiki.o This is rendered with `The text on this speed display is {inscription}` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/speed_display/speed_display.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/speed_display/speed_display.json) diff --git a/Docs/Layers/sport_pitch.md b/Docs/Layers/sport_pitch.md index 0fce02a792..6f569e2476 100644 --- a/Docs/Layers/sport_pitch.md +++ b/Docs/Layers/sport_pitch.md @@ -76,8 +76,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -241,6 +239,28 @@ This tagrendering has no question and is thus read-only +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/sport_places_without_etymology.md b/Docs/Layers/sport_places_without_etymology.md index ea0a3a0b9b..f32dd6d673 100644 --- a/Docs/Layers/sport_places_without_etymology.md +++ b/Docs/Layers/sport_places_without_etymology.md @@ -169,6 +169,18 @@ This tagrendering has no question and is thus read-only -This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` +This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/themes/etymology/etymology.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/etymology/etymology.json) diff --git a/Docs/Layers/sport_shops.md b/Docs/Layers/sport_shops.md index 12d2f03417..f6819729d3 100644 --- a/Docs/Layers/sport_shops.md +++ b/Docs/Layers/sport_shops.md @@ -78,8 +78,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -514,20 +512,6 @@ This tagrendering has no question and is thus read-only -Shows the reviews module (including the possibility to leave a review) - -This tagrendering has no question and is thus read-only - - - - - -### questions - - - -Show the images block at this location - This tagrendering has no question and is thus read-only @@ -538,7 +522,15 @@ This tagrendering has no question and is thus read-only -Shows a small map with the feature. Added by default to every popup +This tagrendering has no question and is thus read-only + + + + + +### move-button + + This tagrendering has no question and is thus read-only @@ -546,6 +538,38 @@ This tagrendering has no question and is thus read-only +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/sports_centre.md b/Docs/Layers/sports_centre.md index 331c6429b2..2b5f59f737 100644 --- a/Docs/Layers/sports_centre.md +++ b/Docs/Layers/sports_centre.md @@ -73,8 +73,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -173,6 +171,48 @@ The question is *Is this place accessible with a wheelchair?* +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/stairs.md b/Docs/Layers/stairs.md index 5a208d727d..5ec8964634 100644 --- a/Docs/Layers/stairs.md +++ b/Docs/Layers/stairs.md @@ -73,8 +73,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -190,6 +188,38 @@ The question is *Is there a ramp at these stairs?* - Unselecting this answer will add ramp:stroller=no - *There is no ramp at these stairs* corresponds with `ramp=no` - Unselecting this answer will add - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/stairs/stairs.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/stairs/stairs.json) diff --git a/Docs/Layers/street_lamps.md b/Docs/Layers/street_lamps.md index ecb3cc3a7a..3b67ed8ee1 100644 --- a/Docs/Layers/street_lamps.md +++ b/Docs/Layers/street_lamps.md @@ -76,8 +76,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -237,6 +235,68 @@ This is rendered with `This lamp points towards {light:direction}` -This tagrendering is only visible in the popup if the following condition is met: `light:count=1` +This tagrendering is only visible in the popup if the following condition is met: `light:count=1` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/street_lamps/street_lamps.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/street_lamps/street_lamps.json) diff --git a/Docs/Layers/streets_without_etymology.md b/Docs/Layers/streets_without_etymology.md index 15c77d4af4..bb2d1f08ab 100644 --- a/Docs/Layers/streets_without_etymology.md +++ b/Docs/Layers/streets_without_etymology.md @@ -170,6 +170,18 @@ This tagrendering has no question and is thus read-only -This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` +This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/themes/etymology/etymology.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/etymology/etymology.json) diff --git a/Docs/Layers/surveillance_camera.md b/Docs/Layers/surveillance_camera.md index d650e3a6a1..03c99e7b82 100644 --- a/Docs/Layers/surveillance_camera.md +++ b/Docs/Layers/surveillance_camera.md @@ -78,8 +78,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -233,6 +231,38 @@ This is rendered with `Mounting method: {camera:mount}` - *This camera is placed on the ceiling* corresponds with `camera:mount=ceiling` - *This camera is placed on a street light* corresponds with `camera:mount=street_lamp` - *This camera is placed on a tree* corresponds with `camera:mount=tree` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/surveillance_camera/surveillance_camera.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/surveillance_camera/surveillance_camera.json) diff --git a/Docs/Layers/tertiary_education.md b/Docs/Layers/tertiary_education.md index b44e4af6c0..87badd8bae 100644 --- a/Docs/Layers/tertiary_education.md +++ b/Docs/Layers/tertiary_education.md @@ -194,6 +194,38 @@ This is rendered with `{phone}` - *{contact:phone}* corresponds with `contact:phone~.+` - This option cannot be chosen as answer - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/tertiary_education/tertiary_education.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/tertiary_education/tertiary_education.json) diff --git a/Docs/Layers/ticket_machine.md b/Docs/Layers/ticket_machine.md index 0f1b2d106b..a332c2d2b4 100644 --- a/Docs/Layers/ticket_machine.md +++ b/Docs/Layers/ticket_machine.md @@ -61,8 +61,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -184,6 +182,38 @@ The question is *what notes can you use to pay here?* - *500 euro notes are accepted* corresponds with `payment:notes:denominations=500 EUR` -This tagrendering is only visible in the popup if the following condition is met: `payment:notes=yes|payment:cash=yes&_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk` +This tagrendering is only visible in the popup if the following condition is met: `payment:notes=yes|payment:cash=yes&_country=at|_country=be|_country=cy|_country=de|_country=ee|_country=es|_country=fi|_country=fr|_country=gr|_country=hr|_country=ie|_country=it|_country=lt|_country=lu|_country=lv|_country=mt|_country=nl|_country=pt|_country=si|_country=sk` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/ticket_machine/ticket_machine.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/ticket_machine/ticket_machine.json) diff --git a/Docs/Layers/ticket_validator.md b/Docs/Layers/ticket_validator.md index 02ae2fea1a..7c26064a43 100644 --- a/Docs/Layers/ticket_validator.md +++ b/Docs/Layers/ticket_validator.md @@ -59,8 +59,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -142,6 +140,38 @@ The question is *Which methods of payment are accepted here?* - Unselecting this answer will add payment:OV-Chipkaart=no - *This ticket validator accepts OV-Chipkaart* corresponds with `payment:ov-chipkaart=yes` - Unselecting this answer will add payment:ov-chipkaart=no - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/ticket_validator/ticket_validator.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/ticket_validator/ticket_validator.json) diff --git a/Docs/Layers/toekomstige_fietsstraat.md b/Docs/Layers/toekomstige_fietsstraat.md index a1c17d9927..387bc80a26 100644 --- a/Docs/Layers/toekomstige_fietsstraat.md +++ b/Docs/Layers/toekomstige_fietsstraat.md @@ -69,7 +69,15 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + This tagrendering has no question and is thus read-only @@ -77,6 +85,28 @@ This tagrendering has no question and is thus read-only +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + ### is_cyclestreet @@ -136,24 +166,10 @@ This tagrendering is only visible in the popup if the following condition is met -### questions +### split-button -Show the images block at this location - -This tagrendering has no question and is thus read-only - - - - - -### minimap - - - -Shows a small map with the feature. Added by default to every popup - This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/toilet.md b/Docs/Layers/toilet.md index 130ff8482d..9ef41b14f0 100644 --- a/Docs/Layers/toilet.md +++ b/Docs/Layers/toilet.md @@ -83,8 +83,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -358,6 +356,68 @@ This is rendered with `{description}` +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### move-button + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/toilet_at_amenity.md b/Docs/Layers/toilet_at_amenity.md index df443602c9..7d4b6103ee 100644 --- a/Docs/Layers/toilet_at_amenity.md +++ b/Docs/Layers/toilet_at_amenity.md @@ -81,8 +81,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -321,6 +319,58 @@ This is rendered with `{toilets:description}` +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/toursistic_places_without_etymology.md b/Docs/Layers/toursistic_places_without_etymology.md index ac50ea11cb..db9d4927c1 100644 --- a/Docs/Layers/toursistic_places_without_etymology.md +++ b/Docs/Layers/toursistic_places_without_etymology.md @@ -169,6 +169,18 @@ This tagrendering has no question and is thus read-only -This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` +This tagrendering is only visible in the popup if the following condition is met: `wikidata~.+` + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/themes/etymology/etymology.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/etymology/etymology.json) diff --git a/Docs/Layers/trail.md b/Docs/Layers/trail.md index c3cc2f638e..cfec0df1f5 100644 --- a/Docs/Layers/trail.md +++ b/Docs/Layers/trail.md @@ -61,8 +61,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -164,6 +162,38 @@ The question is *Is deze wandeltocht toegankelijk met de buggy?* - *deze wandeltocht is toegankelijk met de buggy* corresponds with `pushchair=yes` - *deze wandeltocht is niet toegankelijk met de buggy* corresponds with `pushchair=no` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/trail/trail.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/trail/trail.json) diff --git a/Docs/Layers/transit_routes.md b/Docs/Layers/transit_routes.md index 2097edc060..ab37c72735 100644 --- a/Docs/Layers/transit_routes.md +++ b/Docs/Layers/transit_routes.md @@ -167,6 +167,38 @@ This rendering asks information about the property [operator](https://wiki.open This is rendered with `This bus line is operated by {operator}` - + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` This document is autogenerated from [assets/layers/transit_routes/transit_routes.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/transit_routes/transit_routes.json) diff --git a/Docs/Layers/transit_stops.md b/Docs/Layers/transit_stops.md index 38db6b0bb3..1908e9547d 100644 --- a/Docs/Layers/transit_stops.md +++ b/Docs/Layers/transit_stops.md @@ -96,8 +96,6 @@ This is rendered with `This stop is called {name}` -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -242,6 +240,38 @@ This tagrendering is only visible in the popup if the following condition is met +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + #### Filters diff --git a/Docs/Layers/tree_node.md b/Docs/Layers/tree_node.md index 3c4ad44b2f..77b05c9f18 100644 --- a/Docs/Layers/tree_node.md +++ b/Docs/Layers/tree_node.md @@ -78,8 +78,6 @@ attribute | type | values which are supported by this layer -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - This tagrendering has no question and is thus read-only @@ -277,6 +275,68 @@ This is rendered with `ghost bike
is a memorial for a cyclist who died in a traffic accident, in the form of a white bicycle placed permanently near the accident location -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, ghostbikes -}} -{{service_item -|name= [https://mapcomplete.osm.be/hackerspaces hackerspaces] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:es|en}}, {{#language:ca|en}} -|descr= A MapComplete theme: A map of hackerspaces -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, hackerspaces -}} -{{service_item -|name= [https://mapcomplete.osm.be/healthcare healthcare] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:ca|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:nl|en}}, {{#language:cs|en}}, {{#language:es|en}} -|descr= A MapComplete theme: On this map, various healthcare related items are shown -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, healthcare -}} -{{service_item -|name= [https://mapcomplete.osm.be/hotels hotels] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:da|en}}, {{#language:nb_NO|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:cs|en}}, {{#language:es|en}}, {{#language:ca|en}} -|descr= A MapComplete theme: On this map, you'll find hotels in your area -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, hotels -}} -{{service_item -|name= [https://mapcomplete.osm.be/indoors indoors] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:nl|en}}, {{#language:cs|en}}, {{#language:nb_NO|en}}, {{#language:es|en}}, {{#language:ca|en}} -|descr= A MapComplete theme: On this map, publicly accessible indoor places are shown -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, indoors -}} -{{service_item -|name= [https://mapcomplete.osm.be/kerbs_and_crossings kerbs_and_crossings] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:nb_NO|en}}, {{#language:es|en}}, {{#language:ca|en}} -|descr= A MapComplete theme: A map showing kerbs and crossings -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, kerbs_and_crossings -}} -{{service_item -|name= [https://mapcomplete.osm.be/maps maps] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:ca|en}} -|descr= A MapComplete theme: This theme shows all (touristic) maps that OpenStreetMap knows of -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, maps -}} -{{service_item -|name= [https://mapcomplete.osm.be/maxspeed maxspeed] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:nl|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:es|en}}, {{#language:ca|en}} -|descr= A MapComplete theme: This map shows the legally allowed maximum speed on every road. -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, maxspeed -}} -{{service_item -|name= [https://mapcomplete.osm.be/nature nature] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:ca|en}} -|descr= A MapComplete theme: A map for nature lovers, with interesting POI's -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, nature -}} -{{service_item -|name= [https://mapcomplete.osm.be/notes notes] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:hu|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:ca|en}} -|descr= A MapComplete theme: A note is a pin on the map with some text to indicate something wrong -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, notes -}} -{{service_item -|name= [https://mapcomplete.osm.be/observation_towers observation_towers] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:nb_NO|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:ca|en}} -|descr= A MapComplete theme: Publicly accessible towers to enjoy the view -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, observation_towers -}} -{{service_item -|name= [https://mapcomplete.osm.be/onwheels onwheels] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:nl|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:es|en}}, {{#language:ca|en}} -|descr= A MapComplete theme: On this map, publicly weelchair accessible places are shown and can be easily added -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, onwheels -}} -{{service_item -|name= [https://mapcomplete.osm.be/openwindpowermap openwindpowermap] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:fr|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:nl|en}}, {{#language:da|en}}, {{#language:nb_NO|en}}, {{#language:cs|en}}, {{#language:ca|en}} -|descr= A MapComplete theme: A map for showing and editing wind turbines -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, openwindpowermap -}} -{{service_item -|name= [https://mapcomplete.osm.be/osm_community_index osm_community_index] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:es|en}}, {{#language:ca|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: An index of community resources for OpenStreetMap. -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, osm_community_index -}} -{{service_item -|name= [https://mapcomplete.osm.be/parkings parkings] -|region= Worldwide -|lang= {{#language:nl|en}}, {{#language:en|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:nb_NO|en}}, {{#language:zh_Hant|en}}, {{#language:id|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: This map shows different parking spots -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, parkings -}} -{{service_item -|name= [https://mapcomplete.osm.be/pets pets] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:da|en}}, {{#language:de|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: On this map, you'll find various interesting places for you pets: veterinarians, dog parks, pet shops, dog-friendly restaurants, -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, pets -}} -{{service_item -|name= [https://mapcomplete.osm.be/postboxes postboxes] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:nb_NO|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: A map showing postboxes and post offices -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, postboxes -}} -{{service_item -|name= [https://mapcomplete.osm.be/rainbow_crossings rainbow_crossings] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:nl|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: On this map, rainbow-painted pedestrian crossings are shown and can be easily added -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, rainbow_crossings -}} -{{service_item -|name= [https://mapcomplete.osm.be/shops shops] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:fr|en}}, {{#language:ja|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:nl|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: An editable map with basic shop information -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, shops -}} -{{service_item -|name= [https://mapcomplete.osm.be/sport_pitches sport_pitches] -|region= Worldwide -|lang= {{#language:nl|en}}, {{#language:fr|en}}, {{#language:en|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:ru|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: A map showing sport pitches -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, sport_pitches -}} -{{service_item -|name= [https://mapcomplete.osm.be/sports sports] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: Map showing sport facilities. -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, sports -}} -{{service_item -|name= [https://mapcomplete.osm.be/street_lighting street_lighting] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:de|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:nb_NO|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: On this map you can find everything about street lighting -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, street_lighting -}} -{{service_item -|name= [https://mapcomplete.osm.be/surveillance surveillance] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:fr|en}}, {{#language:pl|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:da|en}}, {{#language:nb_NO|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: Surveillance cameras and other means of surveillance -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, surveillance -}} -{{service_item -|name= [https://mapcomplete.osm.be/transit transit] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:nl|en}}, {{#language:nb_NO|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: Plan your trip with the help of the public transport system -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, transit -}} -{{service_item -|name= [https://mapcomplete.osm.be/trees trees] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:it|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:ru|en}}, {{#language:pl|en}}, {{#language:de|en}}, {{#language:nb_NO|en}}, {{#language:hu|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: Map all the trees -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, trees -}} -{{service_item -|name= [https://mapcomplete.osm.be/waste_basket waste_basket] -|region= Worldwide -|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:fr|en}}, {{#language:nb_NO|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} -|descr= A MapComplete theme: A map with waste baskets -|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} -|image= MapComplete_Screenshot.png -|genre= POI, editor, waste_basket -}} -|} \ No newline at end of file From b0052d3a36e9236cc29a681567372220e34d0ea0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 15 Apr 2023 03:15:17 +0200 Subject: [PATCH 040/257] refactoring: Fix documentation generation, (auto)remove documentation of deleted themes --- Docs/BuiltinIndex.md | 26 +- Docs/BuiltinLayers.md | 850 +++++++++++------- Docs/BuiltinQuestions.md | 13 +- Docs/Hotkeys.md | 8 +- Docs/Layers/cluster_style.md | 56 -- Docs/Layers/filters.md | 106 --- Docs/Layers/grass_in_parks.md | 75 -- Docs/Layers/hackerspaces.md | 227 ----- Docs/Layers/icons.md | 174 ---- Docs/Layers/id_presets.md | 398 -------- Docs/Layers/maybe_climbing.md | 346 ------- Docs/Layers/note.md | 235 +++++ Docs/Layers/note_import.md | 126 --- Docs/Layers/usersettings.md | 272 ------ Docs/Layers/watermill.md | 112 --- Docs/SpecialInputElements.md | 12 +- Docs/TagInfo/mapcomplete_advertising.json | 10 +- Docs/TagInfo/mapcomplete_entrances.json | 265 ------ Docs/TagInfo/mapcomplete_ghostbikes.json | 2 +- Docs/TagInfo/mapcomplete_governments.json | 63 -- Docs/TagInfo/mapcomplete_notes.json | 13 - Docs/TagInfo/mapcomplete_personal.json | 225 ++++- Docs/TagInfo/mapcomplete_surveillance.json | 8 + Docs/TagInfo/mapcomplete_uk_addresses.json | 39 - .../mapcomplete_walls_and_buildings.json | 223 ----- Docs/TagInfo/mapcomplete_waste.json | 10 +- Docs/URL_Parameters.md | 8 - Docs/wikiIndex.txt | 499 ++++++++++ Models/ThemeConfig/LayerConfig.ts | 6 +- UI/DefaultGUI.ts | 10 +- UI/Popup/FeatureInfoBox.ts | 10 +- package.json | 2 +- scripts/generateDocs.ts | 39 +- 33 files changed, 1565 insertions(+), 2903 deletions(-) delete mode 100644 Docs/Layers/cluster_style.md delete mode 100644 Docs/Layers/filters.md delete mode 100644 Docs/Layers/grass_in_parks.md delete mode 100644 Docs/Layers/hackerspaces.md delete mode 100644 Docs/Layers/icons.md delete mode 100644 Docs/Layers/id_presets.md delete mode 100644 Docs/Layers/maybe_climbing.md create mode 100644 Docs/Layers/note.md delete mode 100644 Docs/Layers/note_import.md delete mode 100644 Docs/Layers/usersettings.md delete mode 100644 Docs/Layers/watermill.md delete mode 100644 Docs/TagInfo/mapcomplete_entrances.json delete mode 100644 Docs/TagInfo/mapcomplete_governments.json delete mode 100644 Docs/TagInfo/mapcomplete_notes.json delete mode 100644 Docs/TagInfo/mapcomplete_uk_addresses.json delete mode 100644 Docs/TagInfo/mapcomplete_walls_and_buildings.json diff --git a/Docs/BuiltinIndex.md b/Docs/BuiltinIndex.md index 4010f485db..d90b346710 100644 --- a/Docs/BuiltinIndex.md +++ b/Docs/BuiltinIndex.md @@ -10,6 +10,7 @@ 1. [Index of builtin TagRendering](#index-of-builtin-tagrendering) - [Existing builtin tagrenderings](#existing-builtin-tagrenderings) + [images](#images) + + [luminous_or_lit](#luminous_or_lit) + [wikipedia](#wikipedia) + [bench.*bench-questions](#bench*bench-questions) + [opening_hours](#opening_hours) @@ -41,7 +42,6 @@ + [climbing.max_difficulty](#climbingmax_difficulty) + [climbing.sportclimbing](#climbingsportclimbing) + [climbing.max_bolts](#climbingmax_bolts) - + [all_tags](#all_tags) + [opening_hours_by_appointment](#opening_hours_by_appointment) + [multilevels](#multilevels) + [induction-loop](#induction-loop) @@ -78,6 +78,7 @@ + - advertising - ambulancestation - artwork - atm @@ -117,7 +118,6 @@ - food - ghost_bike - governments - - grass_in_parks - hackerspace - hotel - hydrant @@ -165,6 +165,17 @@ +### luminous_or_lit + + + + + + - advertising + + + + ### wikipedia @@ -664,17 +675,6 @@ -### all_tags - - - - - - - cluster_style - - - - ### opening_hours_by_appointment diff --git a/Docs/BuiltinLayers.md b/Docs/BuiltinLayers.md index 96a9487009..c1ed07dddd 100644 --- a/Docs/BuiltinLayers.md +++ b/Docs/BuiltinLayers.md @@ -30,41 +30,62 @@ + [upload_to_osm](#upload_to_osm) + [minimap](#minimap) + [delete](#delete) -1. [type_node](#type_node) + + [leftover-questions](#leftover-questions) +1. [range](#range) - [Basic tags for this layer](#basic-tags-for-this-layer) - [Supported attributes](#supported-attributes) -1. [note](#note) +1. [last_click](#last_click) - [Basic tags for this layer](#basic-tags-for-this-layer) - [Supported attributes](#supported-attributes) - + [conversation](#conversation) - + [add_image](#add_image) - + [comment](#comment) - + [nearby-images](#nearby-images) - + [report-contributor](#report-contributor) - + [report-note](#report-note) + + [add_new](#add_new) + + [add_note](#add_note) + + [leftover-questions](#leftover-questions) + + [minimap](#minimap) * [Filters](#filters) -1. [import_candidate](#import_candidate) - - [Basic tags for this layer](#basic-tags-for-this-layer) - - [Supported attributes](#supported-attributes) - + [all_tags](#all_tags) -1. [direction](#direction) - - [Basic tags for this layer](#basic-tags-for-this-layer) - - [Supported attributes](#supported-attributes) 1. [conflation](#conflation) - [Basic tags for this layer](#basic-tags-for-this-layer) - [Supported attributes](#supported-attributes) -1. [left_right_style](#left_right_style) - - [Basic tags for this layer](#basic-tags-for-this-layer) - - [Supported attributes](#supported-attributes) 1. [split_point](#split_point) - [Basic tags for this layer](#basic-tags-for-this-layer) - [Supported attributes](#supported-attributes) 1. [current_view](#current_view) + * [Themes using this layer](#themes-using-this-layer) - [Basic tags for this layer](#basic-tags-for-this-layer) - [Supported attributes](#supported-attributes) + + [leftover-questions](#leftover-questions) + + [minimap](#minimap) 1. [matchpoint](#matchpoint) - [Basic tags for this layer](#basic-tags-for-this-layer) - [Supported attributes](#supported-attributes) +1. [import_candidate](#import_candidate) + - [Basic tags for this layer](#basic-tags-for-this-layer) + - [Supported attributes](#supported-attributes) + + [all_tags](#all_tags) + + [leftover-questions](#leftover-questions) + + [minimap](#minimap) +1. [usersettings](#usersettings) + - [Basic tags for this layer](#basic-tags-for-this-layer) + - [Supported attributes](#supported-attributes) + + [profile](#profile) + + [language_picker](#language_picker) + + [inbox](#inbox) + + [settings-link](#settings-link) + + [logout](#logout) + + [picture-license](#picture-license) + + [all-questions-at-once](#all-questions-at-once) + + [translations-title](#translations-title) + + [translation-mode](#translation-mode) + + [translation-help](#translation-help) + + [translation-completeness](#translation-completeness) + + [translation-links](#translation-links) + + [verified-mastodon](#verified-mastodon) + + [cscount-thanks](#cscount-thanks) + + [translation-thanks](#translation-thanks) + + [contributor-thanks](#contributor-thanks) + + [show_debug](#show_debug) + + [debug](#debug) + + [leftover-questions](#leftover-questions) + + [minimap](#minimap) 1. [Normal layers](#normal-layers) @@ -85,15 +106,14 @@ MapComplete has a few data layers available in the theme which have special prop - [gps_location_history](#gps_location_history) - [home_location](#home_location) - [gps_track](#gps_track) - - [type_node](#type_node) - - [note](#note) - - [import_candidate](#import_candidate) - - [direction](#direction) + - [range](#range) + - [last_click](#last_click) - [conflation](#conflation) - - [left_right_style](#left_right_style) - [split_point](#split_point) - [current_view](#current_view) - [matchpoint](#matchpoint) + - [import_candidate](#import_candidate) + - [usersettings](#usersettings) @@ -129,7 +149,7 @@ Elements must have the all of following tags to be shown on this layer: - - selected=yes + @@ -148,7 +168,7 @@ Elements must have the all of following tags to be shown on this layer: -Meta layer showing the current location of the user. Add this to your theme and override the icon to change the appearance of the current location. The object will always have `id=gps` and will have _all_ the properties included in the [`Coordinates`-object](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates) returned by the browser. +Meta layer showing the current location of the user. Add this to your theme and override the icon to change the appearance of the current location. The object will always have `id=gps` and will have _all_ the properties included in the [`Coordinates`-object](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates) (except latitude and longitude) returned by the browser, such as `speed`, `altitude`, `heading`, .... @@ -172,7 +192,7 @@ Elements must have the all of following tags to be shown on this layer: - - id=gps + @@ -217,7 +237,7 @@ Elements must have the all of following tags to be shown on this layer: - - user:location=yes + @@ -260,7 +280,7 @@ Elements must have the all of following tags to be shown on this layer: - - user:home=yes + @@ -303,7 +323,7 @@ Elements must have the all of following tags to be shown on this layer: - - id=location_track + @@ -329,8 +349,6 @@ This tagrendering has no question and is thus read-only -Shows a button to export this feature as GPX. Especially useful for route relations - This tagrendering has no question and is thus read-only @@ -341,8 +359,6 @@ This tagrendering has no question and is thus read-only -Shows a button to export this feature as geojson. Especially useful for debugging or using this in other programs - This tagrendering has no question and is thus read-only @@ -363,8 +379,6 @@ This tagrendering has no question and is thus read-only -Shows a small map with the feature. Added by default to every popup - This tagrendering has no question and is thus read-only @@ -381,21 +395,34 @@ This tagrendering has no question and is thus read-only - type_node -=========== +### leftover-questions + + + +This tagrendering has no question and is thus read-only -This is a priviliged meta_layer which exports _every_ point in OSM. This only works if zoomed below the point that the full tile is loaded (and not loaded via Overpass). Note that this point will also contain a property `parent_ways` which contains all the ways this node is part of as a list. This is mainly used for extremely specialized themes, which do advanced conflations. Expert use only. + range +======= + + + + + +Meta-layer, simply showing a bbox in red - - This layer is shown at zoomlevel **18** and higher + - This layer is shown at zoomlevel **0** and higher + - **This layer is included automatically in every theme. This layer might contain no points** + - Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable. + - Not visible in the layer selection by default. If you want to make this layer toggable, override `name` - Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings` @@ -410,7 +437,7 @@ Elements must have the all of following tags to be shown on this layer: - - id~^(node\/.*)$ + @@ -422,202 +449,14 @@ Elements must have the all of following tags to be shown on this layer: - note -====== + last_click +============ - +
{renderings}{first_preset}
' height="100px"> -This layer shows notes on OpenStreetMap. Having this layer in your theme will trigger the 'add new note' functionality in the 'addNewPoint'-popup (or if your theme has no presets, it'll enable adding notes) - - - - - - - - This layer is shown at zoomlevel **10** and higher - - This layer is loaded from an external source, namely `https://api.openstreetmap.org/api/0.6/notes.json?limit=10000&closed=7&bbox={x_min},{y_min},{x_max},{y_max}` - - - - - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - id~.+ - - - - - Supported attributes ----------------------- - - - - - -### conversation - - - -This tagrendering has no question and is thus read-only - - - - - -### add_image - - - -This tagrendering has no question and is thus read-only - - - - - -### comment - - - -This tagrendering has no question and is thus read-only - - - - - -### nearby-images - - - -This tagrendering has no question and is thus read-only - - - - - -### report-contributor - - - -This tagrendering has no question and is thus read-only - - - -This tagrendering is only visible in the popup if the following condition is met: `_opened_by_anonymous_user=false` - - - -### report-note - - - -This tagrendering has no question and is thus read-only - - - - - -#### Filters - - - - - -id | question | osmTags | fields ----- | ---------- | --------- | -------- -search.0 | Should mention {search} in the first comment | | search (string) - - - - -id | question | osmTags | fields ----- | ---------- | --------- | -------- -not.0 | Should not mention {search} in the first comment | | search (string) - - - - -id | question | osmTags | fields ----- | ---------- | --------- | -------- -opened_by.0 | Opened by contributor {search} | | search (string) - - - - -id | question | osmTags | fields ----- | ---------- | --------- | -------- -not_opened_by.0 | Not opened by contributor {search} | | search (string) - - - - -id | question | osmTags | fields ----- | ---------- | --------- | -------- -edited_by.0 | Last edited by contributor {search} | | search (string) - - - - -id | question | osmTags | fields ----- | ---------- | --------- | -------- -not_edited_by.0 | Opened after {search} | | search (string) - - - - -id | question | osmTags | fields ----- | ---------- | --------- | -------- -opened_before.0 | Created before {search} | | search (date) - - - - -id | question | osmTags | fields ----- | ---------- | --------- | -------- -opened_after.0 | Created after {search} | | search (date) - - - - -id | question | osmTags ----- | ---------- | --------- -anonymous.0 | Only show notes opened by an anonymous contributor | _opened_by_anonymous_user=true - - - - -id | question | osmTags ----- | ---------- | --------- -is_open.0 | Only show open notes | - - - - -id | question | osmTags ----- | ---------- | --------- -no_imports.0 | All Notes (default) | -no_imports.1 | Hide import notes | -no_imports.2 | Show only import Notes | _is_import_note~.+ - - - - - import_candidate -================== - - - - - -Layer used in the importHelper +This layer defines how to render the 'last click'-location. By default, it will show a marker with the possibility to add a new point (if there are some presets) and/or to add a new note (if the 'note' layer attribute is set). If none are possible, this layer won't show up @@ -625,6 +464,7 @@ Layer used in the importHelper - This layer is shown at zoomlevel **0** and higher + - **This layer is included automatically in every theme. This layer might contain no points** - Not visible in the layer selection by default. If you want to make this layer toggable, override `name` @@ -651,7 +491,31 @@ Elements must have the all of following tags to be shown on this layer: -### all_tags +### add_new + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `has_presets=yes` + + + +### add_note + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `has_note_layer=yes` + + + +### leftover-questions @@ -661,43 +525,25 @@ This tagrendering has no question and is thus read-only - direction -=========== +### minimap - - -This layer visualizes directions +This tagrendering has no question and is thus read-only - - - This layer is shown at zoomlevel **16** and higher - - Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable. +#### Filters - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - camera:direction~.+|direction~.+ - - - - - Supported attributes ----------------------- +id | question | osmTags +---- | ---------- | --------- +action.0 | only_if_action_is_possible | has_note_layer=yes\|has_presets=yes @@ -731,51 +577,10 @@ Elements must have the all of following tags to be shown on this layer: - - move=yes|newpoint=yes - Supported attributes ----------------------- - - - - - - left_right_style -================== - - - - - -Special meta-style which will show one single line, either on the left or on the right depending on the id. This is used in the small popups with left_right roads. Cannot be included in a theme - - - - - - - - This layer is shown at zoomlevel **0** and higher - - This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data. - - - - - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - id=left|id=right - - - Supported attributes ---------------------- @@ -813,7 +618,7 @@ Elements must have the all of following tags to be shown on this layer: - - _split_point=yes + @@ -847,6 +652,20 @@ The icon on the button is the default icon of the layer, but can be customized b +#### Themes using this layer + + + + + + - [grb](https://mapcomplete.osm.be/grb) + - [mapcomplete-changes](https://mapcomplete.osm.be/mapcomplete-changes) + - [onwheels](https://mapcomplete.osm.be/onwheels) + - [personal](https://mapcomplete.osm.be/personal) + + + + Basic tags for this layer --------------------------- @@ -856,7 +675,7 @@ Elements must have the all of following tags to be shown on this layer: - - current_view=yes + @@ -868,6 +687,26 @@ Elements must have the all of following tags to be shown on this layer: +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + matchpoint ============ @@ -909,6 +748,410 @@ Elements must have the all of following tags to be shown on this layer: + import_candidate +================== + + + + + +Layer used as template in the importHelper + + + + + + + - This layer is shown at zoomlevel **0** and higher + - This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data. + + + + + Basic tags for this layer +--------------------------- + + + +Elements must have the all of following tags to be shown on this layer: + + + + + + + + + Supported attributes +---------------------- + + + + + +### all_tags + + + +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + + usersettings +============== + + + + + +A special layer which is not meant to be shown on a map, but which is used to set user settings + + + + + + + - This layer is shown at zoomlevel **0** and higher + - This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data. + + + + + Basic tags for this layer +--------------------------- + + + +Elements must have the all of following tags to be shown on this layer: + + + + + + + + + Supported attributes +---------------------- + + + +Warning: + +this quick overview is incomplete + + + +attribute | type | values which are supported by this layer +----------- | ------ | ------------------------------------------ +[](https://taginfo.openstreetmap.org/keys/mapcomplete-pictures-license#values) [mapcomplete-pictures-license](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-pictures-license) | Multiple choice | [CC0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC0) [CC-BY 4.0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC-BY 4.0) [CC-BY-SA 4.0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC-BY-SA 4.0) +[](https://taginfo.openstreetmap.org/keys/mapcomplete-show-all-questions#values) [mapcomplete-show-all-questions](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-show-all-questions) | Multiple choice | [true](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show-all-questions%3Dtrue) [false](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show-all-questions%3Dfalse) +[](https://taginfo.openstreetmap.org/keys/mapcomplete-translation-mode#values) [mapcomplete-translation-mode](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-translation-mode) | Multiple choice | [false](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dfalse) [true](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dtrue) [mobile](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dmobile) +[](https://taginfo.openstreetmap.org/keys/mapcomplete-translation-mode#values) [mapcomplete-translation-mode](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-translation-mode) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dyes) +[](https://taginfo.openstreetmap.org/keys/_translation_percentage#values) [_translation_percentage](https://wiki.openstreetmap.org/wiki/Key:_translation_percentage) | Multiple choice | [100](https://wiki.openstreetmap.org/wiki/Tag:_translation_percentage%3D100) +[](https://taginfo.openstreetmap.org/keys/mapcomplete-show_debug#values) [mapcomplete-show_debug](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-show_debug) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_debug%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_debug%3Dno) + + + + +### profile + + + +This tagrendering has no question and is thus read-only + + + + + +### language_picker + + + +This tagrendering has no question and is thus read-only + + + + + +### inbox + + + +This tagrendering has no question and is thus read-only + + + + + + - *{link(Open your inbox,&LBRACE_backend&RBRACE/messages/inbox,)}* corresponds with `_unreadMessages=0` + - *{link(You have &LBRACE_unreadMessages&RBRACE
Open your inbox,&LBRACE_backend&RBRACE/messages/inbox,)}* corresponds with `_unreadMessages>0` + + + + +### settings-link + + + +This tagrendering has no question and is thus read-only + + + + + +### logout + + + +This tagrendering has no question and is thus read-only + + + + + +### picture-license + + + +This question is not meant to be placed on an OpenStreetMap-element; however it is used in the user information panel to ask which license the user wants + +The question is *Under what license do you want to publish your pictures?* + + + + + + - *Pictures you take will be licensed with CC0 and added to the public domain. This means that everyone can use your pictures for any purpose. This is the default choice.* corresponds with `` + - This option cannot be chosen as answer + - *Pictures you take will be licensed with CC0 and added to the public domain. This means that everyone can use your pictures for any purpose.* corresponds with `mapcomplete-pictures-license=CC0` + - *Pictures you take will be licensed with CC-BY 4.0 which requires everyone using your picture that they have to attribute you* corresponds with `mapcomplete-pictures-license=CC-BY 4.0` + - *Pictures you take will be licensed with CC-BY-SA 4.0 which means that everyone using your picture must attribute you and that derivatives of your picture must be reshared with the same license.* corresponds with `mapcomplete-pictures-license=CC-BY-SA 4.0` + + + + +### all-questions-at-once + + + +The question is *Should questions for unknown data fields appear one-by-one or together?* + + + + + + - *Show all questions in the infobox together* corresponds with `mapcomplete-show-all-questions=true` + - *Show questions one-by-one* corresponds with `mapcomplete-show-all-questions=false` + + + + +### translations-title + + + +This tagrendering has no question and is thus read-only + + + + + +### translation-mode + + + +The question is *Do you want to help translating MapComplete?* + + + + + + - *Don't show a button to quickly change translations* corresponds with `mapcomplete-translation-mode=false` + - *Show a button to quickly open translations when using MapComplete on a big screen* corresponds with `mapcomplete-translation-mode=true` + - *Always show the translation buttons, including on mobile* corresponds with `mapcomplete-translation-mode=mobile` + + + + +### translation-help + + + +This tagrendering has no question and is thus read-only + + + + + + - *Click the 'translate'-icon next to a string to enter or update a piece of text. You need a Weblate-account for this. Create one with your OSM-username to automatically unlock translation mode.* corresponds with `mapcomplete-translation-mode=yes|mapcomplete-translation-mode=true|mapcomplete-translation-mode=mobile` + + + + +### translation-completeness + + + +This tagrendering has no question and is thus read-only + + + + + + - *Completely translated* corresponds with `_translation_percentage=100` + + +This tagrendering is only visible in the popup if the following condition is met: `mapcomplete-translation-mode=yes|mapcomplete-translation-mode=true|mapcomplete-translation-mode=mobile` + + + +### translation-links + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_translation_links~.+&mapcomplete-translation-mode=true|mapcomplete-translation-mode=mobile` + + + +### verified-mastodon + + + +This tagrendering has no question and is thus read-only + + + + + + - *A link to your Mastodon-profile has been been found: {_mastodon_link}* corresponds with `_mastodon_link~.+` + - *We found a link to what looks to be a mastodon account, but it is unverified. Edit your profile description and place the following there: <a href="{_mastodon_candidate}" rel="me">Mastodon</a>* corresponds with `_mastodon_candidate~.+` + + + + +### cscount-thanks + + + +This tagrendering has no question and is thus read-only + + + + + + - *You have made changes on {_csCount} different occasions! That is awesome!* corresponds with `_csCount>0` + + + + +### translation-thanks + + + +This tagrendering has no question and is thus read-only + + + + + + - *You have contributed to translating MapComplete! That's awesome!* corresponds with `_translation_contributions>0` + + + + +### contributor-thanks + + + +This tagrendering has no question and is thus read-only + + + + + + - *You have contributed code to MapComplete with {_code_contributions} commits! That's awesome!* corresponds with `_code_contributions>0` + - This option cannot be chosen as answer + + + + +### show_debug + + + +The question is *Show user settings debug info?* + + + + + + - *Show debug info* corresponds with `mapcomplete-show_debug=yes` + - *Don't show debug info* corresponds with `mapcomplete-show_debug=no` + - *Don't show debug info* corresponds with `` + - This option cannot be chosen as answer + + + + +### debug + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `mapcomplete-show_debug=yes` + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + Normal layers =============== @@ -919,6 +1162,7 @@ The following layers are included in MapComplete: - [address](./Layers/address.md) + - [advertising](./Layers/advertising.md) - [ambulancestation](./Layers/ambulancestation.md) - [artwork](./Layers/artwork.md) - [atm](./Layers/atm.md) @@ -947,7 +1191,6 @@ The following layers are included in MapComplete: - [climbing_opportunity](./Layers/climbing_opportunity.md) - [climbing_route](./Layers/climbing_route.md) - [clock](./Layers/clock.md) - - [cluster_style](./Layers/cluster_style.md) - [conflation](./Layers/conflation.md) - [crab_address](./Layers/crab_address.md) - [crossings](./Layers/crossings.md) @@ -973,7 +1216,6 @@ The following layers are included in MapComplete: - [gps_location](./Layers/gps_location.md) - [gps_location_history](./Layers/gps_location_history.md) - [gps_track](./Layers/gps_track.md) - - [grass_in_parks](./Layers/grass_in_parks.md) - [hackerspace](./Layers/hackerspace.md) - [home_location](./Layers/home_location.md) - [hospital](./Layers/hospital.md) @@ -986,7 +1228,7 @@ The following layers are included in MapComplete: - [information_board](./Layers/information_board.md) - [kerbs](./Layers/kerbs.md) - [kindergarten_childcare](./Layers/kindergarten_childcare.md) - - [left_right_style](./Layers/left_right_style.md) + - [last_click](./Layers/last_click.md) - [map](./Layers/map.md) - [maproulette](./Layers/maproulette.md) - [maproulette_challenge](./Layers/maproulette_challenge.md) @@ -1012,6 +1254,7 @@ The following layers are included in MapComplete: - [public_bookcase](./Layers/public_bookcase.md) - [railway_platforms](./Layers/railway_platforms.md) - [rainbow_crossings](./Layers/rainbow_crossings.md) + - [range](./Layers/range.md) - [reception_desk](./Layers/reception_desk.md) - [recycling](./Layers/recycling.md) - [school](./Layers/school.md) @@ -1036,7 +1279,6 @@ The following layers are included in MapComplete: - [transit_routes](./Layers/transit_routes.md) - [transit_stops](./Layers/transit_stops.md) - [tree_node](./Layers/tree_node.md) - - [type_node](./Layers/type_node.md) - [usersettings](./Layers/usersettings.md) - [veterinary](./Layers/veterinary.md) - [viewpoint](./Layers/viewpoint.md) diff --git a/Docs/BuiltinQuestions.md b/Docs/BuiltinQuestions.md index 84c2582374..7a9e6dca1d 100644 --- a/Docs/BuiltinQuestions.md +++ b/Docs/BuiltinQuestions.md @@ -34,7 +34,6 @@ The following items can be easily reused in your layers + [payment-options-advanced](#payment-options-advanced) + [denominations-coins](#denominations-coins) + [denominations-notes](#denominations-notes) - + [last_edit](#last_edit) + [all_tags](#all_tags) + [multilevels](#multilevels) + [level](#level) @@ -53,6 +52,8 @@ The following items can be easily reused in your layers +{questions()} + *Read-only tagrendering* @@ -379,16 +380,6 @@ what notes can you use to pay here? -### last_edit - - - - - -*Read-only tagrendering* - - - ### all_tags diff --git a/Docs/Hotkeys.md b/Docs/Hotkeys.md index 1ac7bbd614..ce94922a17 100644 --- a/Docs/Hotkeys.md +++ b/Docs/Hotkeys.md @@ -17,14 +17,8 @@ MapComplete supports the following keys: Key combination | Action ----------------- | -------- -`B` | Opens the Background, layers and filters panel `Escape` | Close the sidebar -`L` | Pan the map to the current location or zoom the map to the current location. Requests geopermission -`M` | Select a background layer of category map -`O` | Select a background layer of category osmbasedmap -`P` | Select a background layer of category photo -`ctrl+F` | Select the search bar to search locations -`shift+O` | Sets the background layer to OpenStreetMap-carto +`b` | Opens the Background, layers and filters panel This document is autogenerated from diff --git a/Docs/Layers/cluster_style.md b/Docs/Layers/cluster_style.md deleted file mode 100644 index 19209617f8..0000000000 --- a/Docs/Layers/cluster_style.md +++ /dev/null @@ -1,56 +0,0 @@ -[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources) - - cluster_style -=============== - - - - - -The style for the clustering in all themes. Enable `debug=true` to peak into clustered tiles - - - - - - - - This layer is shown at zoomlevel **0** and higher - - Not visible in the layer selection by default. If you want to make this layer toggable, override `name` - - - - - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - tileId~.+ - - -[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B%22tileId%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) - - - - Supported attributes ----------------------- - - - - - -### all_tags - - - -Shows a table with all the tags of the feature - -This tagrendering has no question and is thus read-only - - - -This document is autogenerated from [assets/layers/cluster_style/cluster_style.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/cluster_style/cluster_style.json) diff --git a/Docs/Layers/filters.md b/Docs/Layers/filters.md deleted file mode 100644 index 5fc43fe3ce..0000000000 --- a/Docs/Layers/filters.md +++ /dev/null @@ -1,106 +0,0 @@ -[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources) - - filters -========= - - - - - -This layer acts as library for common filters - - - - - - - - This layer is shown at zoomlevel **0** and higher - - Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable. - - Not visible in the layer selection by default. If you want to make this layer toggable, override `name` - - Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings` - - - - - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - id~.+ - - -[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B%22id%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) - - - - Supported attributes ----------------------- - - - - - -#### Filters - - - - - -id | question | osmTags ----- | ---------- | --------- -open_now.0 | Opened now | _isOpen=yes - - - - -id | question | osmTags ----- | ---------- | --------- -accepts_cash.0 | Accepts cash | payment:cash=yes - - - - -id | question | osmTags ----- | ---------- | --------- -accepts_cards.0 | Accepts payment cards | payment:cards=yes - - - - -id | question | osmTags ----- | ---------- | --------- -has_image.0 | With and without images (default) | -has_image.1 | Has at least one image | image~.+\|image:0~.+|image:1~.+|image:2~.+|image:3~.+|mapillary~.+ -has_image.2 | Probably does not have an image | - - - - -id | question | osmTags ----- | ---------- | --------- -tactile_paving.0 | With tactile paving | tactile_paving=yes - - - - -id | question | osmTags ----- | ---------- | --------- -tactile_paving_advanced.0 | With or without tactile paving (default) | -tactile_paving_advanced.1 | With tactile paving | tactile_paving=yes -tactile_paving_advanced.2 | Without tactile paving | tactile_paving=no -tactile_paving_advanced.3 | No information about tactile paving | - - - - -id | question | osmTags ----- | ---------- | --------- -has_organic.0 | Has organic options | organic=yes\|organic=only - - -This document is autogenerated from [assets/layers/filters/filters.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/filters/filters.json) diff --git a/Docs/Layers/grass_in_parks.md b/Docs/Layers/grass_in_parks.md deleted file mode 100644 index d28cf40196..0000000000 --- a/Docs/Layers/grass_in_parks.md +++ /dev/null @@ -1,75 +0,0 @@ -[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources) - - grass_in_parks -================ - - - - - -Searches for all accessible grass patches within public parks - these are 'groenzones' - - - - - - - - This layer is shown at zoomlevel **0** and higher - - - - - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - name=Park Oude God|landuse=grass&access=public|access=yes - - -[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B%22name%22%3D%22Park%20Oude%20God%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B%22landuse%22%3D%22grass%22%5D%5B%22access%22%3D%22public%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B%22landuse%22%3D%22grass%22%5D%5B%22access%22%3D%22yes%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) - - - - Supported attributes ----------------------- - - - - - -### images - - - -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - -This tagrendering has no question and is thus read-only - - - - - -### explanation - - - -This tagrendering has no question and is thus read-only - - - - - -### grass-in-parks-reviews - - - -This tagrendering has no question and is thus read-only - - - -This document is autogenerated from [assets/layers/grass_in_parks/grass_in_parks.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/grass_in_parks/grass_in_parks.json) diff --git a/Docs/Layers/hackerspaces.md b/Docs/Layers/hackerspaces.md deleted file mode 100644 index 3c0e8953dd..0000000000 --- a/Docs/Layers/hackerspaces.md +++ /dev/null @@ -1,227 +0,0 @@ - - - hackerspaces -============== - - - - - -Hackerspace - - - - - - - - This layer is shown at zoomlevel **8** and higher - - - - -#### Themes using this layer - - - - - - - [hackerspaces](https://mapcomplete.osm.be/hackerspaces) - - - - - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - leisure=hackerspace - - -[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B%22leisure%22%3D%22hackerspace%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) - - - - Supported attributes ----------------------- - - - -**Warning** This quick overview is incomplete - - - -attribute | type | values which are supported by this layer ------------ | ------ | ------------------------------------------ -[](https://taginfo.openstreetmap.org/keys/hackerspace#values) [hackerspace](https://wiki.openstreetmap.org/wiki/Key:hackerspace) | Multiple choice | [makerspace](https://wiki.openstreetmap.org/wiki/Tag:hackerspace%3Dmakerspace) [](https://wiki.openstreetmap.org/wiki/Tag:hackerspace%3D) -[](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) | -[](https://taginfo.openstreetmap.org/keys/website#values) [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) | -[](https://taginfo.openstreetmap.org/keys/email#values) [email](https://wiki.openstreetmap.org/wiki/Key:email) | [email](../SpecialInputElements.md#email) | -[](https://taginfo.openstreetmap.org/keys/phone#values) [phone](https://wiki.openstreetmap.org/wiki/Key:phone) | [phone](../SpecialInputElements.md#phone) | -[](https://taginfo.openstreetmap.org/keys/opening_hours#values) [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) | [opening_hours](../SpecialInputElements.md#opening_hours) | [24/7](https://wiki.openstreetmap.org/wiki/Tag:opening_hours%3D24/7) -[](https://taginfo.openstreetmap.org/keys/wheelchair#values) [wheelchair](https://wiki.openstreetmap.org/wiki/Key:wheelchair) | Multiple choice | [designated](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Ddesignated) [yes](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dyes) [limited](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dlimited) [no](https://wiki.openstreetmap.org/wiki/Tag:wheelchair%3Dno) -[](https://taginfo.openstreetmap.org/keys/drink:club-mate#values) [drink:club-mate](https://wiki.openstreetmap.org/wiki/Key:drink:club-mate) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:drink:club-mate%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:drink:club-mate%3Dno) -[](https://taginfo.openstreetmap.org/keys/start_date#values) [start_date](https://wiki.openstreetmap.org/wiki/Key:start_date) | [date](../SpecialInputElements.md#date) | - - - - -### is_makerspace - - - -The question is **Is this a hackerspace or a makerspace?** - - - - - - - **This is a makerspace** corresponds with hackerspace=makerspace - - **This is a traditional (software oriented) hackerspace** corresponds with - - - - -### hackerspaces-name - - - -The question is **What is the name of this hackerspace?** - -This rendering asks information about the property [name](https://wiki.openstreetmap.org/wiki/Key:name) -This is rendered with `This hackerspace is named {name}` - - - -### website - - - -The question is **What is the website of {title()}?** - -This rendering asks information about the property [website](https://wiki.openstreetmap.org/wiki/Key:website) -This is rendered with `{website}` - - - - - **{contact:website}** corresponds with contact:website~^..*$_This option cannot be chosen as answer_ - - - - -### email - - - -The question is **What is the email address of {title()}?** - -This rendering asks information about the property [email](https://wiki.openstreetmap.org/wiki/Key:email) -This is rendered with `{email}` - - - - - **{contact:email}** corresponds with contact:email~^..*$_This option cannot be chosen as answer_ - - - - -### phone - - - -The question is **What is the phone number of {title()}?** - -This rendering asks information about the property [phone](https://wiki.openstreetmap.org/wiki/Key:phone) -This is rendered with `{phone}` - - - - - **{contact:phone}** corresponds with contact:phone~^..*$_This option cannot be chosen as answer_ - - - - -### hackerspaces-opening_hours - - - -The question is **When is this hackerspace opened?** - -This rendering asks information about the property [opening_hours](https://wiki.openstreetmap.org/wiki/Key:opening_hours) -This is rendered with `{opening_hours_table()}` - - - - - **Opened 24/7** corresponds with opening_hours=24/7 - - - - -### wheelchair-access - - - -The question is **Is this place accessible with a wheelchair?** - - - - - - - **This place is specially adapted for wheelchair users** corresponds with wheelchair=designated - - **This place is easily reachable with a wheelchair** corresponds with wheelchair=yes - - **It is possible to reach this place in a wheelchair, but it is not easy** corresponds with wheelchair=limited - - **This place is not reachable with a wheelchair** corresponds with wheelchair=no - - - - -### hs-club-mate - - - -The question is **Does this hackerspace serve Club Mate?** - - - - - - - **This hackerspace serves club mate** corresponds with drink:club-mate=yes - - **This hackerspace does not serve club mate** corresponds with drink:club-mate=no - - - - -### hackerspaces-start_date - - - -The question is **When was this hackerspace founded?** - -This rendering asks information about the property [start_date](https://wiki.openstreetmap.org/wiki/Key:start_date) -This is rendered with `This hackerspace was founded at {start_date}` - - - -### questions - - - -_This tagrendering has no question and is thus read-only_ - - - - - -### minimap - - - -_This tagrendering has no question and is thus read-only_ - - - -This document is autogenerated from [assets/themes/hackerspaces/hackerspaces.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/hackerspaces/hackerspaces.json) \ No newline at end of file diff --git a/Docs/Layers/icons.md b/Docs/Layers/icons.md deleted file mode 100644 index 72222252f9..0000000000 --- a/Docs/Layers/icons.md +++ /dev/null @@ -1,174 +0,0 @@ -[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources) - - icons -======= - - - - - -A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI - - - - - - - - This layer is shown at zoomlevel **0** and higher - - Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable. - - Not visible in the layer selection by default. If you want to make this layer toggable, override `name` - - Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings` - - - - - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - id~.+ - - -[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B%22id%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) - - - - Supported attributes ----------------------- - - - -Warning: - -this quick overview is incomplete - - - -attribute | type | values which are supported by this layer ------------ | ------ | ------------------------------------------ -[](https://taginfo.openstreetmap.org/keys/wikipedia#values) [wikipedia](https://wiki.openstreetmap.org/wiki/Key:wikipedia) | Multiple choice | [](https://wiki.openstreetmap.org/wiki/Tag:wikipedia%3D) -[](https://taginfo.openstreetmap.org/keys/_isOpen#values) [_isOpen](https://wiki.openstreetmap.org/wiki/Key:_isOpen) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:_isOpen%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:_isOpen%3Dno) [](https://wiki.openstreetmap.org/wiki/Tag:_isOpen%3D) [parse_error](https://wiki.openstreetmap.org/wiki/Tag:_isOpen%3Dparse_error) -[](https://taginfo.openstreetmap.org/keys/smoking#values) [smoking](https://wiki.openstreetmap.org/wiki/Key:smoking) | Multiple choice | [no](https://wiki.openstreetmap.org/wiki/Tag:smoking%3Dno) [yes](https://wiki.openstreetmap.org/wiki/Tag:smoking%3Dyes) - - - - -### wikipedialink - - - -This tagrendering has no question and is thus read-only - - - - - - - *WD* corresponds with `` - - -This tagrendering is only visible in the popup if the following condition is met: `wikipedia~.+|wikidata~.+` - -This tagrendering has labels `defaults` - - - -### isOpen - - - -This tagrendering has no question and is thus read-only - - - - - - - *clock:#0f0;ring:#0f0* corresponds with `_isOpen=yes` - - *circle:#f00;clock:#fff* corresponds with `_isOpen=no` - - *clock:#ff0;ring:#ff0* corresponds with `opening_hours~.+` - - *circle:#f0f;clock:#fff* corresponds with `_isOpen=parse_error&opening_hours~.+` - - - - -### phonelink - - - -This tagrendering has no question and is thus read-only - - - -This tagrendering is only visible in the popup if the following condition is met: `phone~.+` - -This tagrendering has labels `defaults` - - - -### emaillink - - - -This tagrendering has no question and is thus read-only - - - -This tagrendering is only visible in the popup if the following condition is met: `email~.+` - -This tagrendering has labels `defaults` - - - -### smokingicon - - - -This tagrendering has no question and is thus read-only - - - - - - - *no-smoking* corresponds with `smoking=no` - - *smoking-allowed* corresponds with `smoking=yes` - - -This tagrendering has labels `defaults` - - - -### sharelink - - - -This tagrendering has no question and is thus read-only - - - -This tagrendering has labels `defaults` - - - -### osmlink - - - -This tagrendering has no question and is thus read-only - - - - - - - ** corresponds with `id~^(.*\/-.*)$` - - ** corresponds with `_backend~.+` - - -This tagrendering is only visible in the popup if the following condition is met: `id~^((node|way|relation)\/[0-9]*)$` - -This tagrendering has labels `defaults` - -This document is autogenerated from [assets/layers/icons/icons.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/icons/icons.json) diff --git a/Docs/Layers/id_presets.md b/Docs/Layers/id_presets.md deleted file mode 100644 index 143e819514..0000000000 --- a/Docs/Layers/id_presets.md +++ /dev/null @@ -1,398 +0,0 @@ -[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources) - - id_presets -============ - - - - - -Layer containing various presets and questions generated by ID. These are meant to be reused in other layers by importing the tagRenderings with `id_preset. - - - - - - - - This layer is shown at zoomlevel **0** and higher - - Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable. - - Not visible in the layer selection by default. If you want to make this layer toggable, override `name` - - Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings` - - - - - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - id~.+ - - -[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B%22id%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) - - - - Supported attributes ----------------------- - - - -Warning: - -this quick overview is incomplete - - - -attribute | type | values which are supported by this layer ------------ | ------ | ------------------------------------------ -[](https://taginfo.openstreetmap.org/keys/shop#values) [shop](https://wiki.openstreetmap.org/wiki/Key:shop) | Multiple choice | [agrarian](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dagrarian) [alcohol](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dalcohol) [anime](https://wiki.openstreetmap.org/wiki/Tag:shop%3Danime) [antiques](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dantiques) [appliance](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dappliance) [art](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dart) [baby_goods](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbaby_goods) [bag](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbag) [bakery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbakery) [bathroom_furnishing](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbathroom_furnishing) [beauty](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbeauty) [bed](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbed) [beverages](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbeverages) [bicycle](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbicycle) [boat](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dboat) [bookmaker](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbookmaker) [books](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbooks) [brewing_supplies](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbrewing_supplies) [butcher](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbutcher) [camera](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcamera) [candles](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcandles) [cannabis](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcannabis) [car](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcar) [car_parts](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcar_parts) [car_repair](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcar_repair) [caravan](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcaravan) [carpet](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcarpet) [catalogue](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcatalogue) [charity](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcharity) [cheese](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcheese) [chemist](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dchemist) [chocolate](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dchocolate) [clothes](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dclothes) [coffee](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcoffee) [collector](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcollector) [computer](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcomputer) [confectionery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dconfectionery) [convenience](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dconvenience) [copyshop](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcopyshop) [cosmetics](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcosmetics) [country_store](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcountry_store) [craft](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcraft) [curtain](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcurtain) [dairy](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddairy) [deli](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddeli) [department_store](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddepartment_store) [doityourself](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddoityourself) [doors](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddoors) [dry_cleaning](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddry_cleaning) [e-cigarette](https://wiki.openstreetmap.org/wiki/Tag:shop%3De-cigarette) [electrical](https://wiki.openstreetmap.org/wiki/Tag:shop%3Delectrical) [electronics](https://wiki.openstreetmap.org/wiki/Tag:shop%3Delectronics) [erotic](https://wiki.openstreetmap.org/wiki/Tag:shop%3Derotic) [fabric](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfabric) [farm](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfarm) [fashion_accessories](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfashion_accessories) [fireplace](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfireplace) [fishing](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfishing) [flooring](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dflooring) [florist](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dflorist) [frame](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dframe) [frozen_food](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfrozen_food) [fuel](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfuel) [funeral_directors](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfuneral_directors) [furniture](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfurniture) [games](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgames) [garden_centre](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgarden_centre) [gas](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgas) [general](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgeneral) [gift](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgift) [greengrocer](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgreengrocer) [hairdresser](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhairdresser) [hairdresser_supply](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhairdresser_supply) [hardware](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhardware) [health_food](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhealth_food) [hearing_aids](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhearing_aids) [herbalist](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dherbalist) [hifi](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhifi) [hobby](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhobby) [household_linen](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhousehold_linen) [houseware](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhouseware) [hunting](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhunting) [interior_decoration](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dinterior_decoration) [jewelry](https://wiki.openstreetmap.org/wiki/Tag:shop%3Djewelry) [kiosk](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dkiosk) [kitchen](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dkitchen) [laundry](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dlaundry) [leather](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dleather) [lighting](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dlighting) [locksmith](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dlocksmith) [lottery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dlottery) [mall](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmall) [massage](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmassage) [medical_supply](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmedical_supply) [military_surplus](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmilitary_surplus) [mobile_phone](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmobile_phone) [model](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmodel) [money_lender](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmoney_lender) [motorcycle](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmotorcycle) [motorcycle_repair](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmotorcycle_repair) [music](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmusic) [musical_instrument](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmusical_instrument) [newsagent](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dnewsagent) [nutrition_supplements](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dnutrition_supplements) [optician](https://wiki.openstreetmap.org/wiki/Tag:shop%3Doptician) [outdoor](https://wiki.openstreetmap.org/wiki/Tag:shop%3Doutdoor) [outpost](https://wiki.openstreetmap.org/wiki/Tag:shop%3Doutpost) [paint](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpaint) [party](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dparty) [pastry](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpastry) [pawnbroker](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpawnbroker) [perfumery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dperfumery) [pet](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpet) [pet_grooming](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpet_grooming) [photo](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dphoto) [pottery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpottery) [printer_ink](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dprinter_ink) [psychic](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpsychic) [pyrotechnics](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpyrotechnics) [radiotechnics](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dradiotechnics) [religion](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dreligion) [rental](https://wiki.openstreetmap.org/wiki/Tag:shop%3Drental) [repair](https://wiki.openstreetmap.org/wiki/Tag:shop%3Drepair) [scuba_diving](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dscuba_diving) [seafood](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dseafood) [second_hand](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dsecond_hand) [sewing](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dsewing) [shoe_repair](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dshoe_repair) [shoes](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dshoes) [spices](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dspices) [sports](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dsports) [stationery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dstationery) [storage_rental](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dstorage_rental) [supermarket](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dsupermarket) [swimming_pool](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dswimming_pool) [tailor](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtailor) [tattoo](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtattoo) [tea](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtea) [telecommunication](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtelecommunication) [ticket](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dticket) [tiles](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtiles) [tobacco](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtobacco) [tool_hire](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtool_hire) [toys](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtoys) [trade](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtrade) [travel_agency](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtravel_agency) [trophy](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtrophy) [tyres](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtyres) [vacuum_cleaner](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dvacuum_cleaner) [variety_store](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dvariety_store) [video](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dvideo) [video_games](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dvideo_games) [watches](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwatches) [water](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwater) [water_sports](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwater_sports) [weapons](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dweapons) [wholesale](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwholesale) [wigs](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwigs) [window_blind](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwindow_blind) [wine](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwine) -[](https://taginfo.openstreetmap.org/keys/shop#values) [shop](https://wiki.openstreetmap.org/wiki/Key:shop) | Multiple choice | [boutique](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dboutique) [fashion](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfashion) [vacant](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dvacant) [yes](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dyes) [agrarian](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dagrarian) [alcohol](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dalcohol) [anime](https://wiki.openstreetmap.org/wiki/Tag:shop%3Danime) [antiques](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dantiques) [appliance](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dappliance) [art](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dart) [baby_goods](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbaby_goods) [bag](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbag) [bakery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbakery) [bathroom_furnishing](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbathroom_furnishing) [beauty](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbeauty) [bed](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbed) [beverages](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbeverages) [bicycle](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbicycle) [boat](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dboat) [bookmaker](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbookmaker) [books](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbooks) [brewing_supplies](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbrewing_supplies) [butcher](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dbutcher) [camera](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcamera) [cannabis](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcannabis) [car](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcar) [car_parts](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcar_parts) [car_repair](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcar_repair) [caravan](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcaravan) [carpet](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcarpet) [catalogue](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcatalogue) [charity](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcharity) [cheese](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcheese) [chocolate](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dchocolate) [clothes](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dclothes) [coffee](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcoffee) [computer](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcomputer) [confectionery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dconfectionery) [copyshop](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcopyshop) [cosmetics](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcosmetics) [country_store](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcountry_store) [curtain](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dcurtain) [dairy](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddairy) [deli](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddeli) [department_store](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddepartment_store) [doityourself](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddoityourself) [doors](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddoors) [dry_cleaning](https://wiki.openstreetmap.org/wiki/Tag:shop%3Ddry_cleaning) [e-cigarette](https://wiki.openstreetmap.org/wiki/Tag:shop%3De-cigarette) [electrical](https://wiki.openstreetmap.org/wiki/Tag:shop%3Delectrical) [electronics](https://wiki.openstreetmap.org/wiki/Tag:shop%3Delectronics) [erotic](https://wiki.openstreetmap.org/wiki/Tag:shop%3Derotic) [fabric](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfabric) [fashion_accessories](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfashion_accessories) [fireplace](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfireplace) [fishing](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfishing) [flooring](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dflooring) [florist](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dflorist) [frame](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dframe) [frozen_food](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfrozen_food) [fuel](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfuel) [funeral_directors](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfuneral_directors) [furniture](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dfurniture) [games](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgames) [garden_centre](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgarden_centre) [gas](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgas) [general](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgeneral) [gift](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgift) [greengrocer](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dgreengrocer) [hairdresser](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhairdresser) [hairdresser_supply](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhairdresser_supply) [hardware](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhardware) [health_food](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhealth_food) [hearing_aids](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhearing_aids) [herbalist](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dherbalist) [hifi](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhifi) [hobby](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhobby) [household_linen](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhousehold_linen) [houseware](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhouseware) [hunting](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dhunting) [interior_decoration](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dinterior_decoration) [jewelry](https://wiki.openstreetmap.org/wiki/Tag:shop%3Djewelry) [kiosk](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dkiosk) [kitchen](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dkitchen) [laundry](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dlaundry) [leather](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dleather) [lighting](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dlighting) [locksmith](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dlocksmith) [mall](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmall) [massage](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmassage) [medical_supply](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmedical_supply) [military_surplus](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmilitary_surplus) [model](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmodel) [money_lender](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmoney_lender) [motorcycle](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmotorcycle) [motorcycle_repair](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmotorcycle_repair) [music](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmusic) [musical_instrument](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dmusical_instrument) [newsagent](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dnewsagent) [nutrition_supplements](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dnutrition_supplements) [optician](https://wiki.openstreetmap.org/wiki/Tag:shop%3Doptician) [outdoor](https://wiki.openstreetmap.org/wiki/Tag:shop%3Doutdoor) [outpost](https://wiki.openstreetmap.org/wiki/Tag:shop%3Doutpost) [paint](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpaint) [party](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dparty) [pastry](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpastry) [pawnbroker](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpawnbroker) [perfumery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dperfumery) [pet](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpet) [pet_grooming](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpet_grooming) [photo](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dphoto) [pottery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpottery) [printer_ink](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dprinter_ink) [psychic](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpsychic) [pyrotechnics](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dpyrotechnics) [radiotechnics](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dradiotechnics) [religion](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dreligion) [rental](https://wiki.openstreetmap.org/wiki/Tag:shop%3Drental) [scuba_diving](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dscuba_diving) [seafood](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dseafood) [second_hand](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dsecond_hand) [sewing](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dsewing) [shoe_repair](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dshoe_repair) [shoes](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dshoes) [spices](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dspices) [sports](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dsports) [stationery](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dstationery) [storage_rental](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dstorage_rental) [supermarket](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dsupermarket) [tailor](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtailor) [tattoo](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtattoo) [tea](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtea) [telecommunication](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtelecommunication) [tiles](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtiles) [tobacco](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtobacco) [tool_hire](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtool_hire) [toys](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtoys) [trade](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtrade) [travel_agency](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtravel_agency) [trophy](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtrophy) [tyres](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dtyres) [vacuum_cleaner](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dvacuum_cleaner) [variety_store](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dvariety_store) [video](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dvideo) [video_games](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dvideo_games) [watches](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwatches) [water](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwater) [weapons](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dweapons) [wholesale](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwholesale) [wigs](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwigs) [window_blind](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwindow_blind) [wine](https://wiki.openstreetmap.org/wiki/Tag:shop%3Dwine) - - - - -### shop_types - - - -This tagrendering has no question and is thus read-only - - - - - - - *Farm Supply Shop* corresponds with `shop=agrarian` - - *Liquor Store* corresponds with `shop=alcohol` - - *Anime / Manga Shop* corresponds with `shop=anime` - - *Antiques Shop* corresponds with `shop=antiques` - - *Appliance Store* corresponds with `shop=appliance` - - *Art Store* corresponds with `shop=art` - - *Baby Goods Store* corresponds with `shop=baby_goods` - - *Bag/Luggage Store* corresponds with `shop=bag` - - *Bakery* corresponds with `shop=bakery` - - *Bathroom Furnishing Store* corresponds with `shop=bathroom_furnishing` - - *Beauty Shop* corresponds with `shop=beauty` - - *Bedding/Mattress Store* corresponds with `shop=bed` - - *Beverage Store* corresponds with `shop=beverages` - - *Bicycle Shop* corresponds with `shop=bicycle` - - *Boat Store* corresponds with `shop=boat` - - *Bookmaker* corresponds with `shop=bookmaker` - - *Book Store* corresponds with `shop=books` - - *Brewing Supply Store* corresponds with `shop=brewing_supplies` - - *Butcher* corresponds with `shop=butcher` - - *Camera Equipment Store* corresponds with `shop=camera` - - *Candle Shop* corresponds with `shop=candles` - - *Cannabis Shop* corresponds with `shop=cannabis` - - *Car Dealership* corresponds with `shop=car` - - *Car Parts Store* corresponds with `shop=car_parts` - - *Car Repair Shop* corresponds with `shop=car_repair` - - *RV Dealership* corresponds with `shop=caravan` - - *Carpet Store* corresponds with `shop=carpet` - - *Catalog Shop* corresponds with `shop=catalogue` - - *Charity Store* corresponds with `shop=charity` - - *Cheese Store* corresponds with `shop=cheese` - - *Drugstore* corresponds with `shop=chemist` - - *Chocolate Store* corresponds with `shop=chocolate` - - *Clothing Store* corresponds with `shop=clothes` - - *Coffee Store* corresponds with `shop=coffee` - - *Collectibles Shop* corresponds with `shop=collector` - - *Computer Store* corresponds with `shop=computer` - - *Candy Store* corresponds with `shop=confectionery` - - *Convenience Store* corresponds with `shop=convenience` - - *Copy Store* corresponds with `shop=copyshop` - - *Cosmetics Store* corresponds with `shop=cosmetics` - - *Country Store* corresponds with `shop=country_store` - - *Arts & Crafts Store* corresponds with `shop=craft` - - *Curtain Store* corresponds with `shop=curtain` - - *Dairy Store* corresponds with `shop=dairy` - - *Deli* corresponds with `shop=deli` - - *Department Store* corresponds with `shop=department_store` - - *DIY Store* corresponds with `shop=doityourself` - - *Door Shop* corresponds with `shop=doors` - - *Dry Cleaner* corresponds with `shop=dry_cleaning` - - *E-Cigarette Shop* corresponds with `shop=e-cigarette` - - *Electrical Equipment Store* corresponds with `shop=electrical` - - *Electronics Store* corresponds with `shop=electronics` - - *Erotic Store* corresponds with `shop=erotic` - - *Fabric Store* corresponds with `shop=fabric` - - *Produce Stand* corresponds with `shop=farm` - - *Fashion Accessories Store* corresponds with `shop=fashion_accessories` - - *Fireplace Store* corresponds with `shop=fireplace` - - *Fishing Shop* corresponds with `shop=fishing` - - *Flooring Supply Shop* corresponds with `shop=flooring` - - *Florist* corresponds with `shop=florist` - - *Framing Shop* corresponds with `shop=frame` - - *Frozen Food Store* corresponds with `shop=frozen_food` - - *Fuel Shop* corresponds with `shop=fuel` - - *Funeral Home* corresponds with `shop=funeral_directors` - - *Furniture Store* corresponds with `shop=furniture` - - *Tabletop Game Store* corresponds with `shop=games` - - *Garden Center* corresponds with `shop=garden_centre` - - *Bottled Gas Shop* corresponds with `shop=gas` - - *General Store* corresponds with `shop=general` - - *Gift Shop* corresponds with `shop=gift` - - *Greengrocer* corresponds with `shop=greengrocer` - - *Hairdresser* corresponds with `shop=hairdresser` - - *Hairdresser Supply Store* corresponds with `shop=hairdresser_supply` - - *Hardware Store* corresponds with `shop=hardware` - - *Health Food Shop* corresponds with `shop=health_food` - - *Hearing Aids Store* corresponds with `shop=hearing_aids` - - *Herbalist* corresponds with `shop=herbalist` - - *Hifi Store* corresponds with `shop=hifi` - - *Hobby Shop* corresponds with `shop=hobby` - - *Household Linen Shop* corresponds with `shop=household_linen` - - *Houseware Store* corresponds with `shop=houseware` - - *Hunting Shop* corresponds with `shop=hunting` - - *Interior Decoration Store* corresponds with `shop=interior_decoration` - - *Jewelry Store* corresponds with `shop=jewelry` - - *Kiosk* corresponds with `shop=kiosk` - - *Kitchen Design Store* corresponds with `shop=kitchen` - - *Laundry* corresponds with `shop=laundry` - - *Leather Store* corresponds with `shop=leather` - - *Lighting Store* corresponds with `shop=lighting` - - *Locksmith* corresponds with `shop=locksmith` - - *Lottery Shop* corresponds with `shop=lottery` - - *Mall* corresponds with `shop=mall` - - *Massage Shop* corresponds with `shop=massage` - - *Medical Supply Store* corresponds with `shop=medical_supply` - - *Military Surplus Store* corresponds with `shop=military_surplus` - - *Mobile Phone Store* corresponds with `shop=mobile_phone` - - *Model Shop* corresponds with `shop=model` - - *Money Lender* corresponds with `shop=money_lender` - - *Motorcycle Dealership* corresponds with `shop=motorcycle` - - *Motorcycle Repair Shop* corresponds with `shop=motorcycle_repair` - - *Music Store* corresponds with `shop=music` - - *Musical Instrument Store* corresponds with `shop=musical_instrument` - - *Newspaper/Magazine Shop* corresponds with `shop=newsagent` - - *Nutrition Supplements Store* corresponds with `shop=nutrition_supplements` - - *Optician* corresponds with `shop=optician` - - *Outdoors Store* corresponds with `shop=outdoor` - - *Online Retailer Outpost* corresponds with `shop=outpost` - - *Paint Store* corresponds with `shop=paint` - - *Party Supply Store* corresponds with `shop=party` - - *Pastry Shop* corresponds with `shop=pastry` - - *Pawn Shop* corresponds with `shop=pawnbroker` - - *Perfume Store* corresponds with `shop=perfumery` - - *Pet Store* corresponds with `shop=pet` - - *Pet Grooming Store* corresponds with `shop=pet_grooming` - - *Photography Store* corresponds with `shop=photo` - - *Pottery Store* corresponds with `shop=pottery` - - *Printer Ink Store* corresponds with `shop=printer_ink` - - *Psychic* corresponds with `shop=psychic` - - *Fireworks Store* corresponds with `shop=pyrotechnics` - - *Radio/Electronic Component Store* corresponds with `shop=radiotechnics` - - *Religious Store* corresponds with `shop=religion` - - *Rental Shop* corresponds with `shop=rental` - - *Repair Shop* corresponds with `shop=repair` - - *Scuba Diving Shop* corresponds with `shop=scuba_diving` - - *Seafood Shop* corresponds with `shop=seafood` - - *Consignment/Thrift Store* corresponds with `shop=second_hand` - - *Sewing Supply Shop* corresponds with `shop=sewing` - - *Shoe Repair Shop* corresponds with `shop=shoe_repair` - - *Shoe Store* corresponds with `shop=shoes` - - *Spice Shop* corresponds with `shop=spices` - - *Sporting Goods Store* corresponds with `shop=sports` - - *Stationery Store* corresponds with `shop=stationery` - - *Storage Rental* corresponds with `shop=storage_rental` - - *Supermarket* corresponds with `shop=supermarket` - - *Pool Supply Store* corresponds with `shop=swimming_pool` - - *Tailor* corresponds with `shop=tailor` - - *Tattoo Parlor* corresponds with `shop=tattoo` - - *Tea Store* corresponds with `shop=tea` - - *Telecom Retail Store* corresponds with `shop=telecommunication` - - *Ticket Seller* corresponds with `shop=ticket` - - *Tile Shop* corresponds with `shop=tiles` - - *Tobacco Shop* corresponds with `shop=tobacco` - - *Tool Rental* corresponds with `shop=tool_hire` - - *Toy Store* corresponds with `shop=toys` - - *Trade Shop* corresponds with `shop=trade` - - *Travel Agency* corresponds with `shop=travel_agency` - - *Trophy Shop* corresponds with `shop=trophy` - - *Tire Store* corresponds with `shop=tyres` - - *Vacuum Cleaner Store* corresponds with `shop=vacuum_cleaner` - - *Variety Store* corresponds with `shop=variety_store` - - *Video Store* corresponds with `shop=video` - - *Video Game Store* corresponds with `shop=video_games` - - *Watches Shop* corresponds with `shop=watches` - - *Drinking Water Shop* corresponds with `shop=water` - - *Watersport/Swim Shop* corresponds with `shop=water_sports` - - *Weapon Shop* corresponds with `shop=weapons` - - *Wholesale Store* corresponds with `shop=wholesale` - - *Wig Shop* corresponds with `shop=wigs` - - *Window Blind Store* corresponds with `shop=window_blind` - - *Wine Shop* corresponds with `shop=wine` - - - - -### shop_rendering - - - -This tagrendering has no question and is thus read-only - - - - - - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=boutique` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=fashion` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=vacant` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=yes` - - *circle:white;./assets/layers/id_presets/fas-tractor.svg* corresponds with `shop=agrarian` - - *circle:white;./assets/layers/id_presets/fas-wine-bottle.svg* corresponds with `shop=alcohol` - - *circle:white;./assets/layers/id_presets/fas-dragon.svg* corresponds with `shop=anime` - - *circle:white;./assets/layers/id_presets/temaki-furniture.svg* corresponds with `shop=antiques` - - *circle:white;./assets/layers/id_presets/temaki-laundry.svg* corresponds with `shop=appliance` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=art` - - *circle:white;./assets/layers/id_presets/fas-baby-carriage.svg* corresponds with `shop=baby_goods` - - *circle:white;./assets/layers/id_presets/fas-suitcase-rolling.svg* corresponds with `shop=bag` - - *circle:white;./assets/layers/id_presets/maki-bakery.svg* corresponds with `shop=bakery` - - *circle:white;./assets/layers/id_presets/fas-bath.svg* corresponds with `shop=bathroom_furnishing` - - *circle:white;./assets/layers/id_presets/temaki-lipstick.svg* corresponds with `shop=beauty` - - *circle:white;./assets/layers/id_presets/maki-lodging.svg* corresponds with `shop=bed` - - *circle:white;./assets/layers/id_presets/temaki-bottles.svg* corresponds with `shop=beverages` - - *circle:white;./assets/layers/id_presets/maki-bicycle.svg* corresponds with `shop=bicycle` - - *circle:white;./assets/layers/id_presets/temaki-boat.svg* corresponds with `shop=boat` - - *circle:white;./assets/layers/id_presets/temaki-money_hand.svg* corresponds with `shop=bookmaker` - - *circle:white;./assets/layers/id_presets/fas-book.svg* corresponds with `shop=books` - - *circle:white;./assets/layers/id_presets/temaki-storage_fermenter.svg* corresponds with `shop=brewing_supplies` - - *circle:white;./assets/layers/id_presets/temaki-cleaver.svg* corresponds with `shop=butcher` - - *circle:white;./assets/layers/id_presets/fas-camera-retro.svg* corresponds with `shop=camera` - - *circle:white;./assets/layers/id_presets/fas-cannabis.svg* corresponds with `shop=cannabis` - - *circle:white;./assets/layers/id_presets/maki-car.svg* corresponds with `shop=car` - - *circle:white;./assets/layers/id_presets/fas-car-battery.svg* corresponds with `shop=car_parts` - - *circle:white;./assets/layers/id_presets/maki-car-repair.svg* corresponds with `shop=car_repair` - - *circle:white;./assets/layers/id_presets/temaki-camper_trailer.svg* corresponds with `shop=caravan` - - *circle:white;./assets/layers/id_presets/fas-tape.svg* corresponds with `shop=carpet` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=catalogue` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=charity` - - *circle:white;./assets/layers/id_presets/fas-cheese.svg* corresponds with `shop=cheese` - - *circle:white;./assets/layers/id_presets/maki-confectionery.svg* corresponds with `shop=chocolate` - - *circle:white;./assets/layers/id_presets/maki-clothing-store.svg* corresponds with `shop=clothes` - - *circle:white;./assets/layers/id_presets/temaki-coffee.svg* corresponds with `shop=coffee` - - *circle:white;./assets/layers/id_presets/fas-laptop.svg* corresponds with `shop=computer` - - *circle:white;./assets/layers/id_presets/maki-confectionery.svg* corresponds with `shop=confectionery` - - *circle:white;./assets/layers/id_presets/fas-print.svg* corresponds with `shop=copyshop` - - *circle:white;./assets/layers/id_presets/temaki-lipstick.svg* corresponds with `shop=cosmetics` - - *circle:white;./assets/layers/id_presets/fas-hat-cowboy-side.svg* corresponds with `shop=country_store` - - *circle:white;./assets/layers/id_presets/temaki-curtains.svg* corresponds with `shop=curtain` - - *circle:white;./assets/layers/id_presets/fas-cheese.svg* corresponds with `shop=dairy` - - *circle:white;./assets/layers/id_presets/temaki-meat.svg* corresponds with `shop=deli` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=department_store` - - *circle:white;./assets/layers/id_presets/temaki-tools.svg* corresponds with `shop=doityourself` - - *circle:white;./assets/layers/id_presets/fas-door-open.svg* corresponds with `shop=doors` - - *circle:white;./assets/layers/id_presets/temaki-clothes_hanger.svg* corresponds with `shop=dry_cleaning` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=e-cigarette` - - *circle:white;./assets/layers/id_presets/temaki-power.svg* corresponds with `shop=electrical` - - *circle:white;./assets/layers/id_presets/fas-plug.svg* corresponds with `shop=electronics` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=erotic` - - *circle:white;./assets/layers/id_presets/fas-tape.svg* corresponds with `shop=fabric` - - *circle:white;./assets/layers/id_presets/temaki-fashion_accessories.svg* corresponds with `shop=fashion_accessories` - - *circle:white;./assets/layers/id_presets/temaki-fireplace.svg* corresponds with `shop=fireplace` - - *circle:white;./assets/layers/id_presets/temaki-ice_fishing.svg* corresponds with `shop=fishing` - - *circle:white;./assets/layers/id_presets/temaki-tools.svg* corresponds with `shop=flooring` - - *circle:white;./assets/layers/id_presets/maki-florist.svg* corresponds with `shop=florist` - - *circle:white;./assets/layers/id_presets/fas-vector-square.svg* corresponds with `shop=frame` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=frozen_food` - - *circle:white;./assets/layers/id_presets/temaki-propane_tank.svg* corresponds with `shop=fuel` - - *circle:white;./assets/layers/id_presets/maki-cemetery.svg* corresponds with `shop=funeral_directors` - - *circle:white;./assets/layers/id_presets/fas-couch.svg* corresponds with `shop=furniture` - - *circle:white;./assets/layers/id_presets/fas-dice.svg* corresponds with `shop=games` - - *circle:white;./assets/layers/id_presets/maki-garden-centre.svg* corresponds with `shop=garden_centre` - - *circle:white;./assets/layers/id_presets/temaki-propane_tank.svg* corresponds with `shop=gas` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=general` - - *circle:white;./assets/layers/id_presets/maki-gift.svg* corresponds with `shop=gift` - - *circle:white;./assets/layers/id_presets/fas-carrot.svg* corresponds with `shop=greengrocer` - - *circle:white;./assets/layers/id_presets/temaki-beauty_salon.svg* corresponds with `shop=hairdresser` - - *circle:white;./assets/layers/id_presets/temaki-hair_care.svg* corresponds with `shop=hairdresser_supply` - - *circle:white;./assets/layers/id_presets/temaki-tools.svg* corresponds with `shop=hardware` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=health_food` - - *circle:white;./assets/layers/id_presets/temaki-hearing_aid.svg* corresponds with `shop=hearing_aids` - - *circle:white;./assets/layers/id_presets/fas-leaf.svg* corresponds with `shop=herbalist` - - *circle:white;./assets/layers/id_presets/temaki-speaker.svg* corresponds with `shop=hifi` - - *circle:white;./assets/layers/id_presets/fas-dragon.svg* corresponds with `shop=hobby` - - *circle:white;./assets/layers/id_presets/temaki-cloth.svg* corresponds with `shop=household_linen` - - *circle:white;./assets/layers/id_presets/fas-blender.svg* corresponds with `shop=houseware` - - *circle:white;./assets/layers/id_presets/temaki-bow_and_arrow.svg* corresponds with `shop=hunting` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=interior_decoration` - - *circle:white;./assets/layers/id_presets/maki-jewelry-store.svg* corresponds with `shop=jewelry` - - *circle:white;./assets/layers/id_presets/fas-store.svg* corresponds with `shop=kiosk` - - *circle:white;./assets/layers/id_presets/temaki-kitchen_sink.svg* corresponds with `shop=kitchen` - - *circle:white;./assets/layers/id_presets/temaki-laundry.svg* corresponds with `shop=laundry` - - *circle:white;./assets/layers/id_presets/temaki-handbag.svg* corresponds with `shop=leather` - - *circle:white;./assets/layers/id_presets/temaki-desk_lamp.svg* corresponds with `shop=lighting` - - *circle:white;./assets/layers/id_presets/fas-key.svg* corresponds with `shop=locksmith` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=mall` - - *circle:white;./assets/layers/id_presets/temaki-spa.svg* corresponds with `shop=massage` - - *circle:white;./assets/layers/id_presets/fas-crutch.svg* corresponds with `shop=medical_supply` - - *circle:white;./assets/layers/id_presets/temaki-military.svg* corresponds with `shop=military_surplus` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=model` - - *circle:white;./assets/layers/id_presets/temaki-money_hand.svg* corresponds with `shop=money_lender` - - *circle:white;./assets/layers/id_presets/fas-motorcycle.svg* corresponds with `shop=motorcycle` - - *circle:white;./assets/layers/id_presets/temaki-motorcycle_repair.svg* corresponds with `shop=motorcycle_repair` - - *circle:white;./assets/layers/id_presets/fas-compact-disc.svg* corresponds with `shop=music` - - *circle:white;./assets/layers/id_presets/fas-guitar.svg* corresponds with `shop=musical_instrument` - - *circle:white;./assets/layers/id_presets/fas-newspaper.svg* corresponds with `shop=newsagent` - - *circle:white;./assets/layers/id_presets/fas-pills.svg* corresponds with `shop=nutrition_supplements` - - *circle:white;./assets/layers/id_presets/maki-optician.svg* corresponds with `shop=optician` - - *circle:white;./assets/layers/id_presets/temaki-compass.svg* corresponds with `shop=outdoor` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=outpost` - - *circle:white;./assets/layers/id_presets/fas-paint-roller.svg* corresponds with `shop=paint` - - *circle:white;./assets/layers/id_presets/temaki-balloon.svg* corresponds with `shop=party` - - *circle:white;./assets/layers/id_presets/maki-bakery.svg* corresponds with `shop=pastry` - - *circle:white;./assets/layers/id_presets/temaki-money_hand.svg* corresponds with `shop=pawnbroker` - - *circle:white;./assets/layers/id_presets/temaki-perfume.svg* corresponds with `shop=perfumery` - - *circle:white;./assets/layers/id_presets/fas-cat.svg* corresponds with `shop=pet` - - *circle:white;./assets/layers/id_presets/temaki-pet_grooming.svg* corresponds with `shop=pet_grooming` - - *circle:white;./assets/layers/id_presets/fas-camera-retro.svg* corresponds with `shop=photo` - - *circle:white;./assets/layers/id_presets/temaki-vase.svg* corresponds with `shop=pottery` - - *circle:white;./assets/layers/id_presets/fas-print.svg* corresponds with `shop=printer_ink` - - *circle:white;./assets/layers/id_presets/temaki-psychic.svg* corresponds with `shop=psychic` - - *circle:white;./assets/layers/id_presets/temaki-rocket_firework.svg* corresponds with `shop=pyrotechnics` - - *circle:white;./assets/layers/id_presets/fas-microchip.svg* corresponds with `shop=radiotechnics` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=religion` - - *circle:white;./assets/layers/id_presets/fas-dolly.svg* corresponds with `shop=rental` - - *circle:white;./assets/layers/id_presets/temaki-scuba_diving.svg* corresponds with `shop=scuba_diving` - - *circle:white;./assets/layers/id_presets/temaki-fish_cleaning.svg* corresponds with `shop=seafood` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=second_hand` - - *circle:white;./assets/layers/id_presets/temaki-needle_and_spool.svg* corresponds with `shop=sewing` - - *circle:white;./assets/layers/id_presets/temaki-hammer_shoe.svg* corresponds with `shop=shoe_repair` - - *circle:white;./assets/layers/id_presets/maki-shoe.svg* corresponds with `shop=shoes` - - *circle:white;./assets/layers/id_presets/temaki-spice_bottle.svg* corresponds with `shop=spices` - - *circle:white;./assets/layers/id_presets/fas-futbol.svg* corresponds with `shop=sports` - - *circle:white;./assets/layers/id_presets/fas-paperclip.svg* corresponds with `shop=stationery` - - *circle:white;./assets/layers/id_presets/temaki-storage_rental.svg* corresponds with `shop=storage_rental` - - *circle:white;./assets/layers/id_presets/maki-grocery.svg* corresponds with `shop=supermarket` - - *circle:white;./assets/layers/id_presets/temaki-needle_and_spool.svg* corresponds with `shop=tailor` - - *circle:white;./assets/layers/id_presets/temaki-tattoo_machine.svg* corresponds with `shop=tattoo` - - *circle:white;./assets/layers/id_presets/maki-teahouse.svg* corresponds with `shop=tea` - - *circle:white;./assets/layers/id_presets/maki-telephone.svg* corresponds with `shop=telecommunication` - - *circle:white;./assets/layers/id_presets/temaki-tiling.svg* corresponds with `shop=tiles` - - *circle:white;./assets/layers/id_presets/temaki-pipe.svg* corresponds with `shop=tobacco` - - *circle:white;./assets/layers/id_presets/temaki-tools.svg* corresponds with `shop=tool_hire` - - *circle:white;./assets/layers/id_presets/fas-rocket.svg* corresponds with `shop=toys` - - *circle:white;./assets/layers/id_presets/temaki-tools.svg* corresponds with `shop=trade` - - *circle:white;./assets/layers/id_presets/fas-suitcase.svg* corresponds with `shop=travel_agency` - - *circle:white;./assets/layers/id_presets/fas-trophy.svg* corresponds with `shop=trophy` - - *circle:white;./assets/layers/id_presets/temaki-tire.svg* corresponds with `shop=tyres` - - *circle:white;./assets/layers/id_presets/temaki-vacuum.svg* corresponds with `shop=vacuum_cleaner` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=variety_store` - - *circle:white;./assets/layers/id_presets/temaki-movie_rental.svg* corresponds with `shop=video` - - *circle:white;./assets/layers/id_presets/maki-gaming.svg* corresponds with `shop=video_games` - - *circle:white;./assets/layers/id_presets/maki-watch.svg* corresponds with `shop=watches` - - *circle:white;./assets/layers/id_presets/temaki-water_bottle.svg* corresponds with `shop=water` - - *circle:white;./assets/layers/id_presets/temaki-dagger.svg* corresponds with `shop=weapons` - - *circle:white;./assets/layers/id_presets/maki-warehouse.svg* corresponds with `shop=wholesale` - - *circle:white;./assets/layers/id_presets/maki-shop.svg* corresponds with `shop=wigs` - - *circle:white;./assets/layers/id_presets/temaki-window.svg* corresponds with `shop=window_blind` - - *circle:white;./assets/layers/id_presets/maki-alcohol-shop.svg* corresponds with `shop=wine` - - -This document is autogenerated from [assets/layers/id_presets/id_presets.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/id_presets/id_presets.json) diff --git a/Docs/Layers/maybe_climbing.md b/Docs/Layers/maybe_climbing.md deleted file mode 100644 index 06a2965b99..0000000000 --- a/Docs/Layers/maybe_climbing.md +++ /dev/null @@ -1,346 +0,0 @@ - - - maybe_climbing -================ - - - - - -A climbing opportunity? - - - - - - - - This layer is shown at zoomlevel **19** and higher - - This layer will automatically load [climbing](./climbing.md) into the layout as it depends on it: A calculated tag loads features from this layer (calculatedTag[0] which calculates the value for _embedding_feature_properties) - - - - -#### Themes using this layer - - - - - - - [climbing](https://mapcomplete.osm.be/climbing) - - - - - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - leisure=sports_centre|barrier=wall|barrier=retaining_wall|natural=cliff|natural=rock|natural=stone - - - - -[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B!%22climbing%22%5D%5B%22barrier%22%3D%22wall%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B!%22climbing%22%5D%5B%22barrier%22%3D%22retaining_wall%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B!%22climbing%22%5D%5B%22leisure%22%3D%22sports_centre%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B!%22climbing%22%5D%5B%22natural%22%3D%22cliff%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B!%22climbing%22%5D%5B%22natural%22%3D%22rock%22%5D(%7B%7Bbbox%7D%7D)%3B%0A%20%20%20%20nwr%5B!%22climbing%22%5D%5B%22natural%22%3D%22stone%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) - - - - Supported attributes ----------------------- - - - -**Warning** This quick overview is incomplete - - - -attribute | type | values which are supported by this layer ------------ | ------ | ------------------------------------------ -[](https://taginfo.openstreetmap.org/keys/url#values) [url](https://wiki.openstreetmap.org/wiki/Key:url) | [url](../SpecialInputElements.md#url) | -[](https://taginfo.openstreetmap.org/keys/_embedding_feature:access#values) [_embedding_feature:access](https://wiki.openstreetmap.org/wiki/Key:_embedding_feature:access) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:_embedding_feature:access%3Dyes) [permit](https://wiki.openstreetmap.org/wiki/Tag:_embedding_feature:access%3Dpermit) [customers](https://wiki.openstreetmap.org/wiki/Tag:_embedding_feature:access%3Dcustomers) [members](https://wiki.openstreetmap.org/wiki/Tag:_embedding_feature:access%3Dmembers) [no](https://wiki.openstreetmap.org/wiki/Tag:_embedding_feature:access%3Dno) -[](https://taginfo.openstreetmap.org/keys/access#values) [access](https://wiki.openstreetmap.org/wiki/Key:access) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:access%3Dyes) [permit](https://wiki.openstreetmap.org/wiki/Tag:access%3Dpermit) [customers](https://wiki.openstreetmap.org/wiki/Tag:access%3Dcustomers) [members](https://wiki.openstreetmap.org/wiki/Tag:access%3Dmembers) [no](https://wiki.openstreetmap.org/wiki/Tag:access%3Dno) -[](https://taginfo.openstreetmap.org/keys/access:description#values) [access:description](https://wiki.openstreetmap.org/wiki/Key:access:description) | [string](../SpecialInputElements.md#string) | -[](https://taginfo.openstreetmap.org/keys/climbing:length#values) [climbing:length](https://wiki.openstreetmap.org/wiki/Key:climbing:length) | [pnat](../SpecialInputElements.md#pnat) | -[](https://taginfo.openstreetmap.org/keys/climbing:grade:french:min#values) [climbing:grade:french:min](https://wiki.openstreetmap.org/wiki/Key:climbing:grade:french:min) | [string](../SpecialInputElements.md#string) | -[](https://taginfo.openstreetmap.org/keys/climbing:grade:french:max#values) [climbing:grade:french:max](https://wiki.openstreetmap.org/wiki/Key:climbing:grade:french:max) | [string](../SpecialInputElements.md#string) | -[](https://taginfo.openstreetmap.org/keys/climbing:boulder#values) [climbing:boulder](https://wiki.openstreetmap.org/wiki/Key:climbing:boulder) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:climbing:boulder%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:climbing:boulder%3Dno) [limited](https://wiki.openstreetmap.org/wiki/Tag:climbing:boulder%3Dlimited) -[](https://taginfo.openstreetmap.org/keys/climbing:toprope#values) [climbing:toprope](https://wiki.openstreetmap.org/wiki/Key:climbing:toprope) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:climbing:toprope%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:climbing:toprope%3Dno) -[](https://taginfo.openstreetmap.org/keys/climbing:sport#values) [climbing:sport](https://wiki.openstreetmap.org/wiki/Key:climbing:sport) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:climbing:sport%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:climbing:sport%3Dno) -[](https://taginfo.openstreetmap.org/keys/climbing:traditional#values) [climbing:traditional](https://wiki.openstreetmap.org/wiki/Key:climbing:traditional) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:climbing:traditional%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:climbing:traditional%3Dno) -[](https://taginfo.openstreetmap.org/keys/climbing:speed#values) [climbing:speed](https://wiki.openstreetmap.org/wiki/Key:climbing:speed) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:climbing:speed%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:climbing:speed%3Dno) - - - - -### climbing-opportunity-name - - - -_This tagrendering has no question and is thus read-only_ - - - -Only visible if `name~^..*$` is shown - - - -### climbing-possible - - - -The question is **Is climbing possible here?** - - - - - - - **Climbing is possible here** corresponds with sport=climbing - - **Climbing is not possible here** corresponds with climbing=no - - **Climbing is not possible here** corresponds with sport!~^climbing$_This option cannot be chosen as answer_ - - - - -### Website - - - -The question is **Is there a (unofficial) website with more informations (e.g. topos)?** - -This rendering asks information about the property [url](https://wiki.openstreetmap.org/wiki/Key:url) -This is rendered with `{url}` - -Only visible if `leisure!~^sports_centre$&sport=climbing` is shown - - - -### Access from containing feature - - - -_This tagrendering has no question and is thus read-only_ - - - - - - - **The containing feature states that this is publicly accessible
{_embedding_feature:access:description}** corresponds with _embedding_feature:access=yes - - **The containing feature states that a permit is needed to access
{_embedding_feature:access:description}** corresponds with _embedding_feature:access=permit - - **The containing feature states that this is only accessible to customers
{_embedding_feature:access:description}** corresponds with _embedding_feature:access=customers - - **The containing feature states that this is only accessible to club members
{_embedding_feature:access:description}** corresponds with _embedding_feature:access=members - - **Not accessible as stated by the containing feature** corresponds with _embedding_feature:access=no - - -Only visible if `_embedding_feature:access~^..*$` is shown - - - -### Access - - - -The question is **Who can access here?** - - - - - - - **Publicly accessible to anyone** corresponds with access=yes - - **You need a permit to access here** corresponds with access=permit - - **Only customers** corresponds with access=customers - - **Only club members** corresponds with access=members - - **Not accessible** corresponds with access=no - - -Only visible if `climbing!~^no$&sport=climbing|climbing:sport=yes&access~^..*$|` is shown - - - -### Access description (without _embedding_feature:access:description) - - - -_This tagrendering has no question and is thus read-only_ - -This rendering asks information about the property [access:description](https://wiki.openstreetmap.org/wiki/Key:access:description) -This is rendered with `{access:description}` - - - -### Avg length? - - - -The question is **What is the (average) length of the routes in meters?** - -This rendering asks information about the property [climbing:length](https://wiki.openstreetmap.org/wiki/Key:climbing:length) -This is rendered with `The routes are {canonical(climbing:length)} long on average` - -Only visible if `climbing!~^route$&climbing:toprope!~^no$&sport=climbing|climbing:sport=yes|climbing=traditional|climbing=gym` is shown - - - -### Difficulty-min - - - -The question is **What is the grade of the easiest route here, according to the french classification system?** - -This rendering asks information about the property [climbing:grade:french:min](https://wiki.openstreetmap.org/wiki/Key:climbing:grade:french:min) -This is rendered with `The lowest grade is {climbing:grade:french:min} according to the french/belgian system` - -Only visible if `climbing!~^route$&climbing:sport=yes|sport=climbing` is shown - - - -### Difficulty-max - - - -The question is **What is the highest grade route here, according to the french classification system?** - -This rendering asks information about the property [climbing:grade:french:max](https://wiki.openstreetmap.org/wiki/Key:climbing:grade:french:max) -This is rendered with `The highest grade is {climbing:grade:french:max} according to the french/belgian system` - -Only visible if `climbing!~^route$&climbing:sport=yes|sport=climbing` is shown - - - -### Boldering? - - - -The question is **Is bouldering possible here?** - - - - - - - **Bouldering is possible here** corresponds with climbing:boulder=yes - - **Bouldering is not possible here** corresponds with climbing:boulder=no - - **Bouldering is possible, allthough there are only a few routes** corresponds with climbing:boulder=limited - - **There are {climbing:boulder} boulder routes** corresponds with climbing:boulder~^..*$_This option cannot be chosen as answer_ - - -Only visible if `climbing:sport=yes|sport=climbing` is shown - - - -### Toproping? - - - -The question is **Is toprope climbing possible here?** - - - - - - - **Toprope climbing is possible here** corresponds with climbing:toprope=yes - - **Toprope climbing is not possible here** corresponds with climbing:toprope=no - - **There are {climbing:toprope} toprope routes** corresponds with climbing:toprope~^..*$_This option cannot be chosen as answer_ - - -Only visible if `climbing:sport=yes|sport=climbing` is shown - - - -### Sportclimbing? - - - -The question is **Is sport climbing possible here on fixed anchors?** - - - - - - - **Sport climbing is possible here** corresponds with climbing:sport=yes - - **Sport climbing is not possible here** corresponds with climbing:sport=no - - **There are {climbing:sport} sport climbing routes** corresponds with climbing:sport~^..*$_This option cannot be chosen as answer_ - - -Only visible if `climbing:sport=yes|sport=climbing` is shown - - - -### Traditional climbing? - - - -The question is **Is traditional climbing possible here (using own gear e.g. chocks)?** - - - - - - - **Traditional climbing is possible here** corresponds with climbing:traditional=yes - - **Traditional climbing is not possible here** corresponds with climbing:traditional=no - - **There are {climbing:traditional} traditional climbing routes** corresponds with climbing:traditional~^..*$_This option cannot be chosen as answer_ - - -Only visible if `climbing:sport=yes|sport=climbing` is shown - - - -### Speed climbing? - - - -The question is **Is there a speed climbing wall?** - - - - - - - **There is a speed climbing wall** corresponds with climbing:speed=yes - - **There is no speed climbing wall** corresponds with climbing:speed=no - - **There are {climbing:speed} speed climbing walls** corresponds with climbing:speed~^..*$_This option cannot be chosen as answer_ - - -Only visible if `leisure=sports_centre&climbing:sport=yes|sport=climbing` is shown - - - -### questions - - - -_This tagrendering has no question and is thus read-only_ - - - - - -### reviews - - - -_This tagrendering has no question and is thus read-only_ - - - - - -### questions - - - -_This tagrendering has no question and is thus read-only_ - - - - - -### minimap - - - -_This tagrendering has no question and is thus read-only_ - - - -This document is autogenerated from [assets/themes/climbing/climbing.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/themes/climbing/climbing.json) \ No newline at end of file diff --git a/Docs/Layers/note.md b/Docs/Layers/note.md new file mode 100644 index 0000000000..bc97c5d2aa --- /dev/null +++ b/Docs/Layers/note.md @@ -0,0 +1,235 @@ +[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources) + + note +====== + + + + + +This layer shows notes on OpenStreetMap. Having this layer in your theme will trigger the 'add new note' functionality in the 'addNewPoint'-popup (or if your theme has no presets, it'll enable adding notes) + + + + + + + - This layer is shown at zoomlevel **10** and higher + - This layer is loaded from an external source, namely `https://api.openstreetmap.org/api/0.6/notes.json?limit=10000&closed=7&bbox={x_min},{y_min},{x_max},{y_max}` + + + + +#### Themes using this layer + + + + + + - [notes](https://mapcomplete.osm.be/notes) + - [personal](https://mapcomplete.osm.be/personal) + + + + + Basic tags for this layer +--------------------------- + + + +Elements must have the all of following tags to be shown on this layer: + + + + - id~.+ + + +[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B%22id%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) + + + + Supported attributes +---------------------- + + + + + +### conversation + + + +This tagrendering has no question and is thus read-only + + + + + +### add_image + + + +This tagrendering has no question and is thus read-only + + + + + +### comment + + + +This tagrendering has no question and is thus read-only + + + + + +### nearby-images + + + +This tagrendering has no question and is thus read-only + + + + + +### report-contributor + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_opened_by_anonymous_user=false` + + + +### report-note + + + +This tagrendering has no question and is thus read-only + + + + + +### leftover-questions + + + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +This tagrendering has no question and is thus read-only + + + + + +### last_edit + + + +This tagrendering has no question and is thus read-only + + + +This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` + + + +#### Filters + + + + + +id | question | osmTags | fields +---- | ---------- | --------- | -------- +search.0 | Should mention {search} in the first comment | | search (string) + + + + +id | question | osmTags | fields +---- | ---------- | --------- | -------- +not.0 | Should not mention {search} in the first comment | | search (string) + + + + +id | question | osmTags | fields +---- | ---------- | --------- | -------- +opened_by.0 | Opened by contributor {search} | | search (string) + + + + +id | question | osmTags | fields +---- | ---------- | --------- | -------- +not_opened_by.0 | Not opened by contributor {search} | | search (string) + + + + +id | question | osmTags | fields +---- | ---------- | --------- | -------- +edited_by.0 | Last edited by contributor {search} | | search (string) + + + + +id | question | osmTags | fields +---- | ---------- | --------- | -------- +not_edited_by.0 | Opened after {search} | | search (string) + + + + +id | question | osmTags | fields +---- | ---------- | --------- | -------- +opened_before.0 | Created before {search} | | search (date) + + + + +id | question | osmTags | fields +---- | ---------- | --------- | -------- +opened_after.0 | Created after {search} | | search (date) + + + + +id | question | osmTags +---- | ---------- | --------- +anonymous.0 | Only show notes opened by an anonymous contributor | _opened_by_anonymous_user=true + + + + +id | question | osmTags +---- | ---------- | --------- +is_open.0 | Only show open notes | + + + + +id | question | osmTags +---- | ---------- | --------- +no_imports.0 | All Notes (default) | +no_imports.1 | Hide import notes | +no_imports.2 | Show only import Notes | _is_import_note~.+ + + +This document is autogenerated from [assets/layers/note/note.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/note/note.json) diff --git a/Docs/Layers/note_import.md b/Docs/Layers/note_import.md deleted file mode 100644 index ac3a9f5f48..0000000000 --- a/Docs/Layers/note_import.md +++ /dev/null @@ -1,126 +0,0 @@ - - - note_import -============= - - - - - -Template for note note imports. - - - - - - - - This layer is shown at zoomlevel **10** and higher - - This layer is loaded from an external source, namely `https://api.openstreetmap.org/api/0.6/notes.json?closed=0&bbox={x_min},{y_min},{x_max},{y_max}` - - This layer will automatically load [public_bookcase](./public_bookcase.md) into the layout as it depends on it: a tagrendering needs this layer (import) - - - - -#### Themes using this layer - - - - - - - [personal](https://mapcomplete.osm.be/personal) - - - - - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - id~^..*$ - - -[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B%22id%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) - - - - Supported attributes ----------------------- - - - - - -### conversation - - - -This tagrendering has no question and is thus read-only - - - - - -### Intro - - - -This tagrendering has no question and is thus read-only - - - - - -### import - - - -This tagrendering has no question and is thus read-only - - - - - -### close_note_ - - - -This tagrendering has no question and is thus read-only - - - - - -### close_note_mapped - - - -This tagrendering has no question and is thus read-only - - - - - -### comment - - - -This tagrendering has no question and is thus read-only - - - - - -### add_image - - - -This tagrendering has no question and is thus read-only - - - -This document is autogenerated from [assets/layers/note_import/note_import.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/note_import/note_import.json) \ No newline at end of file diff --git a/Docs/Layers/usersettings.md b/Docs/Layers/usersettings.md deleted file mode 100644 index ba823c0704..0000000000 --- a/Docs/Layers/usersettings.md +++ /dev/null @@ -1,272 +0,0 @@ -[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources) - - usersettings -============== - - - - - -A special layer which is not meant to be shown on a map, but which is used to set user settings - - - - - - - - This layer is shown at zoomlevel **0** and higher - - Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable. - - Not visible in the layer selection by default. If you want to make this layer toggable, override `name` - - Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings` - - - - - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - id~.+ - - -[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B%22id%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) - - - - Supported attributes ----------------------- - - - -Warning: - -this quick overview is incomplete - - - -attribute | type | values which are supported by this layer ------------ | ------ | ------------------------------------------ -[](https://taginfo.openstreetmap.org/keys/mapcomplete-pictures-license#values) [mapcomplete-pictures-license](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-pictures-license) | Multiple choice | [CC0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC0) [CC-BY 4.0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC-BY 4.0) [CC-BY-SA 4.0](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-pictures-license%3DCC-BY-SA 4.0) -[](https://taginfo.openstreetmap.org/keys/mapcomplete-show-all-questions#values) [mapcomplete-show-all-questions](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-show-all-questions) | Multiple choice | [true](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show-all-questions%3Dtrue) [false](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show-all-questions%3Dfalse) -[](https://taginfo.openstreetmap.org/keys/mapcomplete-translation-mode#values) [mapcomplete-translation-mode](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-translation-mode) | Multiple choice | [false](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dfalse) [true](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dtrue) [mobile](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dmobile) -[](https://taginfo.openstreetmap.org/keys/mapcomplete-translation-mode#values) [mapcomplete-translation-mode](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-translation-mode) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-translation-mode%3Dyes) -[](https://taginfo.openstreetmap.org/keys/_translation_percentage#values) [_translation_percentage](https://wiki.openstreetmap.org/wiki/Key:_translation_percentage) | Multiple choice | [100](https://wiki.openstreetmap.org/wiki/Tag:_translation_percentage%3D100) -[](https://taginfo.openstreetmap.org/keys/mapcomplete-show_debug#values) [mapcomplete-show_debug](https://wiki.openstreetmap.org/wiki/Key:mapcomplete-show_debug) | Multiple choice | [yes](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_debug%3Dyes) [no](https://wiki.openstreetmap.org/wiki/Tag:mapcomplete-show_debug%3Dno) - - - - -### picture-license - - - -This question is not meant to be placed on an OpenStreetMap-element; however it is used in the user information panel to ask which license the user wants - -The question is *Under what license do you want to publish your pictures?* - - - - - - - *Pictures you take will be licensed with CC0 and added to the public domain. This means that everyone can use your pictures for any purpose. This is the default choice.* corresponds with `` - - This option cannot be chosen as answer - - *Pictures you take will be licensed with CC0 and added to the public domain. This means that everyone can use your pictures for any purpose.* corresponds with `mapcomplete-pictures-license=CC0` - - *Pictures you take will be licensed with CC-BY 4.0 which requires everyone using your picture that they have to attribute you* corresponds with `mapcomplete-pictures-license=CC-BY 4.0` - - *Pictures you take will be licensed with CC-BY-SA 4.0 which means that everyone using your picture must attribute you and that derivatives of your picture must be reshared with the same license.* corresponds with `mapcomplete-pictures-license=CC-BY-SA 4.0` - - - - -### all-questions-at-once - - - -The question is *Should questions for unknown data fields appear one-by-one or together?* - - - - - - - *Show all questions in the infobox together* corresponds with `mapcomplete-show-all-questions=true` - - *Show questions one-by-one* corresponds with `mapcomplete-show-all-questions=false` - - - - -### translations-title - - - -This tagrendering has no question and is thus read-only - - - -This tagrendering is part of group `translations` - - - -### translation-mode - - - -The question is *Do you want to help translating MapComplete?* - - - - - - - *Don't show a button to quickly change translations* corresponds with `mapcomplete-translation-mode=false` - - *Show a button to quickly open translations when using MapComplete on a big screen* corresponds with `mapcomplete-translation-mode=true` - - *Always show the translation buttons, including on mobile* corresponds with `mapcomplete-translation-mode=mobile` - - -This tagrendering is part of group `translations` - - - -### translation-help - - - -This tagrendering has no question and is thus read-only - - - - - - - *Click the 'translate'-icon next to a string to enter or update a piece of text. You need a Weblate-account for this. Create one with your OSM-username to automatically unlock translation mode.* corresponds with `mapcomplete-translation-mode=yes|mapcomplete-translation-mode=true|mapcomplete-translation-mode=mobile` - - -This tagrendering is part of group `translations` - - - -### translation-completeness - - - -This tagrendering has no question and is thus read-only - - - - - - - *Completely translated* corresponds with `_translation_percentage=100` - - -This tagrendering is only visible in the popup if the following condition is met: `mapcomplete-translation-mode=yes|mapcomplete-translation-mode=true|mapcomplete-translation-mode=mobile` - -This tagrendering is part of group `translations` - - - -### translation-links - - - -This tagrendering has no question and is thus read-only - - - -This tagrendering is only visible in the popup if the following condition is met: `_translation_links~.+&mapcomplete-translation-mode=true|mapcomplete-translation-mode=mobile` - -This tagrendering is part of group `translations` - - - -### verified-mastodon - - - -This tagrendering has no question and is thus read-only - - - - - - - *A link to your Mastodon-profile has been been found: {_mastodon_link}* corresponds with `_mastodon_link~.+` - - *We found a link to what looks to be a mastodon account, but it is unverified. Edit your profile description and place the following there: <a href="{_mastodon_candidate}" rel="me">Mastodon</a>* corresponds with `_mastodon_candidate~.+` - - - - -### cscount-thanks - - - -This tagrendering has no question and is thus read-only - - - - - - - *You have made changes on {_csCount} different occasions! That is awesome!* corresponds with `_csCount>0` - - - - -### translation-thanks - - - -This tagrendering has no question and is thus read-only - - - - - - - *You have contributed to translating MapComplete! That's awesome!* corresponds with `_translation_contributions>0` - - - - -### contributor-thanks - - - -This tagrendering has no question and is thus read-only - - - - - - - *You have contributed code to MapComplete with {_code_contributions} commits! That's awesome!* corresponds with `_code_contributions>0` - - This option cannot be chosen as answer - - - - -### show_debug - - - -The question is *Show user settings debug info?* - - - - - - - *Show debug info* corresponds with `mapcomplete-show_debug=yes` - - *Don't show debug info* corresponds with `mapcomplete-show_debug=no` - - *Don't show debug info* corresponds with `` - - This option cannot be chosen as answer - - - - -### debug - - - -This tagrendering has no question and is thus read-only - - - -This tagrendering is only visible in the popup if the following condition is met: `mapcomplete-show_debug=yes` - -This document is autogenerated from [assets/layers/usersettings/usersettings.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/usersettings/usersettings.json) diff --git a/Docs/Layers/watermill.md b/Docs/Layers/watermill.md deleted file mode 100644 index 12f1ea0429..0000000000 --- a/Docs/Layers/watermill.md +++ /dev/null @@ -1,112 +0,0 @@ - - - watermill -=========== - - - - - -Watermolens - - - - - - - - This layer is shown at zoomlevel **12** and higher - - - - - Basic tags for this layer ---------------------------- - - - -Elements must have the all of following tags to be shown on this layer: - - - - - man_made=watermill - - -[Execute on overpass](http://overpass-turbo.eu/?Q=%5Bout%3Ajson%5D%5Btimeout%3A90%5D%3B(%20%20%20%20nwr%5B%22man_made%22%3D%22watermill%22%5D(%7B%7Bbbox%7D%7D)%3B%0A)%3Bout%20body%3B%3E%3Bout%20skel%20qt%3B) - - - - Supported attributes ----------------------- - - - -Warning: - -this quick overview is incomplete - - - -attribute | type | values which are supported by this layer ------------ | ------ | ------------------------------------------ -[](https://taginfo.openstreetmap.org/keys/access:description#values) [access:description](https://wiki.openstreetmap.org/wiki/Key:access:description) | [string](../SpecialInputElements.md#string) | -[](https://taginfo.openstreetmap.org/keys/operator#values) [operator](https://wiki.openstreetmap.org/wiki/Key:operator) | [string](../SpecialInputElements.md#string) | [Natuurpunt](https://wiki.openstreetmap.org/wiki/Tag:operator%3DNatuurpunt) - - - - -### images - - - -This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` - -This tagrendering has no question and is thus read-only - - - - - -### Access tag - - - -The question is *Is dit gebied toegankelijk?* - -This rendering asks information about the property [access:description](https://wiki.openstreetmap.org/wiki/Key:access:description) - -This is rendered with `De toegankelijkheid van dit gebied is: {access:description}` - - - - - - - *Vrij toegankelijk* corresponds with `access=yes` - - *Niet toegankelijk* corresponds with `access=no` - - *Niet toegankelijk, want privégebied* corresponds with `access=private` - - *Toegankelijk, ondanks dat het privegebied is* corresponds with `access=permissive` - - *Enkel toegankelijk met een gids of tijdens een activiteit* corresponds with `access=guided` - - *Toegankelijk mits betaling* corresponds with `access=yes&fee=yes` - - - - -### Operator tag - - - -The question is *Wie beheert dit pad?* - -This rendering asks information about the property [operator](https://wiki.openstreetmap.org/wiki/Key:operator) - -This is rendered with `Beheer door {operator}` - - - - - - - *Dit gebied wordt beheerd door Natuurpunt* corresponds with `operator=Natuurpunt` - - *Dit gebied wordt beheerd door {operator}* corresponds with `operator~^((n|N)atuurpunt.*)$` - - This option cannot be chosen as answer - - -This document is autogenerated from [assets/layers/watermill/watermill.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/watermill/watermill.json) \ No newline at end of file diff --git a/Docs/SpecialInputElements.md b/Docs/SpecialInputElements.md index ed4c9b0f45..9ab2d010fc 100644 --- a/Docs/SpecialInputElements.md +++ b/Docs/SpecialInputElements.md @@ -59,7 +59,7 @@ A date with date picker -A positive number or zero +A whole, positive number or zero @@ -67,7 +67,7 @@ A positive number or zero -A number +A whole number, either positive, negative or zero @@ -170,7 +170,7 @@ A strict positive number -A decimal +A decimal number @@ -178,7 +178,7 @@ A decimal -A positive decimal (inclusive zero) +A positive decimal number or zero @@ -194,7 +194,7 @@ An email adress -The validatedTextField will format URLs to always be valid and have a https://-header (even though the 'https'-part will be hidden from the user +The validatedTextField will format URLs to always be valid and have a https://-header (even though the 'https'-part will be hidden from the user. Furthermore, some tracking parameters will be removed @@ -255,4 +255,4 @@ postfix | Piece of text that will always be added to the end of the generated op Shows a color picker -This document is autogenerated from [UI/Input/ValidatedTextField.ts](https://github.com/pietervdvn/MapComplete/blob/develop/UI/Input/ValidatedTextField.ts) +This document is autogenerated from [UI/InputElement/Validators.ts](https://github.com/pietervdvn/MapComplete/blob/develop/UI/InputElement/Validators.ts) diff --git a/Docs/TagInfo/mapcomplete_advertising.json b/Docs/TagInfo/mapcomplete_advertising.json index 5ee8909099..844b221b40 100644 --- a/Docs/TagInfo/mapcomplete_advertising.json +++ b/Docs/TagInfo/mapcomplete_advertising.json @@ -91,27 +91,27 @@ }, { "key": "animated", - "description": "Layer 'Advertise' shows animated=no with a fixed text, namely 'Static, always shows the same message' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Advertising Map') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign)", + "description": "Layer 'Advertise' shows animated=no with a fixed text, namely 'Static, always shows the same message' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Advertising Map') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign&advertising!=board)", "value": "no" }, { "key": "animated", - "description": "Layer 'Advertise' shows animated=digital_display with a fixed text, namely 'This object has a built-in digital display to show prices or some other message' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Advertising Map') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign)", + "description": "Layer 'Advertise' shows animated=digital_display with a fixed text, namely 'This object has a built-in digital display to show prices or some other message' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Advertising Map') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign&advertising!=board)", "value": "digital_display" }, { "key": "animated", - "description": "Layer 'Advertise' shows animated=trivision_blades with a fixed text, namely 'Trivision - the billboard consists of many triangular prisms which regularly rotate' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Advertising Map') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign)", + "description": "Layer 'Advertise' shows animated=trivision_blades with a fixed text, namely 'Trivision - the billboard consists of many triangular prisms which regularly rotate' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Advertising Map') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign&advertising!=board)", "value": "trivision_blades" }, { "key": "animated", - "description": "Layer 'Advertise' shows animated=winding_posters with a fixed text, namely 'Scrolling posters' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Advertising Map') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign)", + "description": "Layer 'Advertise' shows animated=winding_posters with a fixed text, namely 'Scrolling posters' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Advertising Map') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign&advertising!=board)", "value": "winding_posters" }, { "key": "animated", - "description": "Layer 'Advertise' shows animated=revolving with a fixed text, namely 'Rotates on itself' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Advertising Map') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign)", + "description": "Layer 'Advertise' shows animated=revolving with a fixed text, namely 'Rotates on itself' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Open Advertising Map') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign&advertising!=board)", "value": "revolving" }, { diff --git a/Docs/TagInfo/mapcomplete_entrances.json b/Docs/TagInfo/mapcomplete_entrances.json deleted file mode 100644 index 87bfd16abd..0000000000 --- a/Docs/TagInfo/mapcomplete_entrances.json +++ /dev/null @@ -1,265 +0,0 @@ -{ - "data_format": 1, - "project": { - "name": "MapComplete Entrances", - "description": "Survey entrances to help wheelchair routing", - "project_url": "https://mapcomplete.osm.be/entrances", - "doc_url": "https://github.com/pietervdvn/MapComplete/tree/master/assets/themes/", - "icon_url": "https://mapcomplete.osm.be/assets/layers/entrance/door.svg", - "contact_name": "Pieter Vander Vennet, MapComplete", - "contact_email": "pietervdvn@posteo.net" - }, - "tags": [ - { - "key": "highway", - "description": "The MapComplete theme Entrances has a layer Pedestrian paths showing features with this tag", - "value": "footway" - }, - { - "key": "highway", - "description": "The MapComplete theme Entrances has a layer Pedestrian paths showing features with this tag", - "value": "path" - }, - { - "key": "highway", - "description": "The MapComplete theme Entrances has a layer Pedestrian paths showing features with this tag", - "value": "corridor" - }, - { - "key": "highway", - "description": "The MapComplete theme Entrances has a layer Pedestrian paths showing features with this tag", - "value": "steps" - }, - { - "key": "entrance", - "description": "The MapComplete theme Entrances has a layer Entrance showing features with this tag" - }, - { - "key": "indoor", - "description": "The MapComplete theme Entrances has a layer Entrance showing features with this tag", - "value": "door" - }, - { - "key": "door", - "description": "The MapComplete theme Entrances has a layer Entrance showing features with this tag" - }, - { - "key": "image", - "description": "The layer 'Entrance allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" - }, - { - "key": "mapillary", - "description": "The layer 'Entrance allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" - }, - { - "key": "wikidata", - "description": "The layer 'Entrance allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" - }, - { - "key": "wikipedia", - "description": "The layer 'Entrance allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" - }, - { - "key": "level", - "description": "Layer 'Entrance' shows and asks freeform values for key 'level' (in the MapComplete.osm.be theme 'Entrances')" - }, - { - "key": "location", - "description": "Layer 'Entrance' shows location=underground with a fixed text, namely 'Located underground' (in the MapComplete.osm.be theme 'Entrances')", - "value": "underground" - }, - { - "key": "level", - "description": "Layer 'Entrance' shows level=0 with a fixed text, namely 'Located on the ground floor' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "0" - }, - { - "key": "level", - "description": "Layer 'Entrance' shows with a fixed text, namely 'Located on the ground floor' (in the MapComplete.osm.be theme 'Entrances') Picking this answer will delete the key level.", - "value": "" - }, - { - "key": "level", - "description": "Layer 'Entrance' shows level=1 with a fixed text, namely 'Located on the first floor' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "1" - }, - { - "key": "level", - "description": "Layer 'Entrance' shows level=-1 with a fixed text, namely 'Located on the first basement level' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "-1" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=yes with a fixed text, namely 'No specific entrance type is known' (in the MapComplete.osm.be theme 'Entrances')", - "value": "yes" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows indoor=door with a fixed text, namely 'This is an indoor door, separating a room or a corridor within a single building' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances') Picking this answer will delete the key entrance.", - "value": "" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows indoor=door with a fixed text, namely 'This is an indoor door, separating a room or a corridor within a single building' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "door" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=main with a fixed text, namely 'This is the main entrance' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=main with a fixed text, namely 'This is the main entrance' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "main" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=secondary with a fixed text, namely 'This is a secondary entrance' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=secondary with a fixed text, namely 'This is a secondary entrance' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "secondary" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=service with a fixed text, namely 'This is a service entrance - normally only used for employees, delivery, …' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=service with a fixed text, namely 'This is a service entrance - normally only used for employees, delivery, …' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "service" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=exit with a fixed text, namely 'This is an exit where one can not enter' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=exit with a fixed text, namely 'This is an exit where one can not enter' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "exit" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=entrance with a fixed text, namely 'This is an entrance where one can only enter (but not exit)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=entrance with a fixed text, namely 'This is an entrance where one can only enter (but not exit)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "entrance" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=emergency with a fixed text, namely 'This is emergency exit' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=emergency with a fixed text, namely 'This is emergency exit' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "emergency" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=home with a fixed text, namely 'This is the entrance to a private home' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=home with a fixed text, namely 'This is the entrance to a private home' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "home" - }, - { - "key": "door", - "description": "Layer 'Entrance' shows door=yes with a fixed text, namely 'The door type is not known' (in the MapComplete.osm.be theme 'Entrances')", - "value": "yes" - }, - { - "key": "door", - "description": "Layer 'Entrance' shows door=hinged with a fixed text, namely 'A classical, hinged door supported by joints' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "hinged" - }, - { - "key": "door", - "description": "Layer 'Entrance' shows door=revolving with a fixed text, namely 'A revolving door which hangs on a central shaft, rotating within a cylindrical enclosure' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "revolving" - }, - { - "key": "door", - "description": "Layer 'Entrance' shows door=sliding with a fixed text, namely 'A sliding door where the door slides sidewards, typically parallel with a wall' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "sliding" - }, - { - "key": "door", - "description": "Layer 'Entrance' shows door=overhead with a fixed text, namely 'A door which rolls from overhead, typically seen for garages' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "overhead" - }, - { - "key": "door", - "description": "Layer 'Entrance' shows door=no with a fixed text, namely 'This is an entrance without a physical door' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "no" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=yes with a fixed text, namely 'This is an automatic door' (in the MapComplete.osm.be theme 'Entrances')", - "value": "yes" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=no with a fixed text, namely 'This door is not automated' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "no" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=motion with a fixed text, namely 'This door will open automatically when motion is detected' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "motion" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=floor with a fixed text, namely 'This door will open automatically when a sensor in the floor is triggered' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "floor" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=button with a fixed text, namely 'This door will open automatically when a button is pressed' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "button" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=slowdown_button with a fixed text, namely 'This door revolves automatically all the time, but has a button to slow it down, e.g. for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "slowdown_button" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=continuous with a fixed text, namely 'This door revolves automatically all the time' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "continuous" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=serviced_on_button_press with a fixed text, namely 'This door will be opened by staff when requested by pressing a button' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "serviced_on_button_press" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=serviced_on_request with a fixed text, namely 'This door will be opened by staff when requested' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "serviced_on_request" - }, - { - "key": "width", - "description": "Layer 'Entrance' shows and asks freeform values for key 'width' (in the MapComplete.osm.be theme 'Entrances')" - }, - { - "key": "kerb:height", - "description": "Layer 'Entrance' shows and asks freeform values for key 'kerb:height' (in the MapComplete.osm.be theme 'Entrances')" - }, - { - "key": "kerb:height", - "description": "Layer 'Entrance' shows kerb:height=0 with a fixed text, namely 'This door does not have a kerb' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Entrances')", - "value": "0" - } - ] -} \ No newline at end of file diff --git a/Docs/TagInfo/mapcomplete_ghostbikes.json b/Docs/TagInfo/mapcomplete_ghostbikes.json index b7f1fa4eb2..bfa2348db3 100644 --- a/Docs/TagInfo/mapcomplete_ghostbikes.json +++ b/Docs/TagInfo/mapcomplete_ghostbikes.json @@ -2,7 +2,7 @@ "data_format": 1, "project": { "name": "MapComplete Ghost bikes", - "description": "A ghost bike is a memorial for a cyclist who died in a traffic accident, in the form of a white bicycle placed permanently near the accident location", + "description": "A ", "project_url": "https://mapcomplete.osm.be/ghostbikes", "doc_url": "https://github.com/pietervdvn/MapComplete/tree/master/assets/themes/", "icon_url": "https://mapcomplete.osm.be/assets/themes/ghostbikes/logo.svg", diff --git a/Docs/TagInfo/mapcomplete_governments.json b/Docs/TagInfo/mapcomplete_governments.json deleted file mode 100644 index a120d9068d..0000000000 --- a/Docs/TagInfo/mapcomplete_governments.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "data_format": 1, - "project": { - "name": "MapComplete Governmental Offices", - "description": "On this map, Governmental offices are shown and can be easily added", - "project_url": "https://mapcomplete.osm.be/governments", - "doc_url": "https://github.com/pietervdvn/MapComplete/tree/master/assets/themes/", - "icon_url": "https://mapcomplete.osm.be/assets/themes/onwheels/crest.svg", - "contact_name": "Pieter Vander Vennet, MapComplete", - "contact_email": "pietervdvn@posteo.net" - }, - "tags": [ - { - "key": "office", - "description": "The MapComplete theme Governmental Offices has a layer governments showing features with this tag", - "value": "government" - }, - { - "key": "image", - "description": "The layer 'governments allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" - }, - { - "key": "mapillary", - "description": "The layer 'governments allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" - }, - { - "key": "wikidata", - "description": "The layer 'governments allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" - }, - { - "key": "wikipedia", - "description": "The layer 'governments allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" - }, - { - "key": "phone", - "description": "Layer 'governments' shows and asks freeform values for key 'phone' (in the MapComplete.osm.be theme 'Governmental Offices')" - }, - { - "key": "contact:phone", - "description": "Layer 'governments' shows contact:phone~^..*$ with a fixed text, namely '{contact:phone}' (in the MapComplete.osm.be theme 'Governmental Offices')" - }, - { - "key": "email", - "description": "Layer 'governments' shows and asks freeform values for key 'email' (in the MapComplete.osm.be theme 'Governmental Offices')" - }, - { - "key": "contact:email", - "description": "Layer 'governments' shows contact:email~^..*$ with a fixed text, namely '{contact:email}' (in the MapComplete.osm.be theme 'Governmental Offices')" - }, - { - "key": "website", - "description": "Layer 'governments' shows and asks freeform values for key 'website' (in the MapComplete.osm.be theme 'Governmental Offices')" - }, - { - "key": "contact:website", - "description": "Layer 'governments' shows contact:website~^..*$ with a fixed text, namely '{contact:website}' (in the MapComplete.osm.be theme 'Governmental Offices')" - }, - { - "key": "name", - "description": "Layer 'governments' shows and asks freeform values for key 'name' (in the MapComplete.osm.be theme 'Governmental Offices')" - } - ] -} \ No newline at end of file diff --git a/Docs/TagInfo/mapcomplete_notes.json b/Docs/TagInfo/mapcomplete_notes.json deleted file mode 100644 index f9874de7e1..0000000000 --- a/Docs/TagInfo/mapcomplete_notes.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "data_format": 1, - "project": { - "name": "MapComplete Notes on OpenStreetMap", - "description": "A note is a pin on the map with some text to indicate something wrong", - "project_url": "https://mapcomplete.osm.be/notes", - "doc_url": "https://github.com/pietervdvn/MapComplete/tree/master/assets/themes/", - "icon_url": "https://mapcomplete.osm.be/assets/themes/notes/logo.svg", - "contact_name": "Pieter Vander Vennet, MapComplete", - "contact_email": "pietervdvn@posteo.net" - }, - "tags": [] -} \ No newline at end of file diff --git a/Docs/TagInfo/mapcomplete_personal.json b/Docs/TagInfo/mapcomplete_personal.json index 7419fda989..16b30a1a91 100644 --- a/Docs/TagInfo/mapcomplete_personal.json +++ b/Docs/TagInfo/mapcomplete_personal.json @@ -10,6 +10,213 @@ "contact_email": "pietervdvn@posteo.net" }, "tags": [ + { + "key": "advertising", + "description": "The MapComplete theme Personal theme has a layer Advertise showing features with this tag" + }, + { + "key": "image", + "description": "The layer 'Advertise allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "mapillary", + "description": "The layer 'Advertise allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikidata", + "description": "The layer 'Advertise allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "wikipedia", + "description": "The layer 'Advertise allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" + }, + { + "key": "advertising", + "description": "Layer 'Advertise' shows and asks freeform values for key 'advertising' (in the MapComplete.osm.be theme 'Personal theme')" + }, + { + "key": "advertising", + "description": "Layer 'Advertise' shows advertising=billboard with a fixed text, namely 'This is a billboard' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "billboard" + }, + { + "key": "advertising", + "description": "Layer 'Advertise' shows advertising=board with a fixed text, namely 'This is a board' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "board" + }, + { + "key": "advertising", + "description": "Layer 'Advertise' shows advertising=column with a fixed text, namely 'This is a column' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "column" + }, + { + "key": "advertising", + "description": "Layer 'Advertise' shows advertising=flag with a fixed text, namely 'This is a flag' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "flag" + }, + { + "key": "advertising", + "description": "Layer 'Advertise' shows advertising=poster_box with a fixed text, namely 'This is a poster Box' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "poster_box" + }, + { + "key": "advertising", + "description": "Layer 'Advertise' shows advertising=screen with a fixed text, namely 'This is a screen' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "screen" + }, + { + "key": "advertising", + "description": "Layer 'Advertise' shows advertising=sculpture with a fixed text, namely 'This is a sculpture' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "sculpture" + }, + { + "key": "advertising", + "description": "Layer 'Advertise' shows advertising=sign with a fixed text, namely 'This is a sign' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "sign" + }, + { + "key": "advertising", + "description": "Layer 'Advertise' shows advertising=tarp with a fixed text, namely 'This is a tarp (a weatherproof piece of textile with an advertising message)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "tarp" + }, + { + "key": "advertising", + "description": "Layer 'Advertise' shows advertising=totem with a fixed text, namely 'This is a totem' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "totem" + }, + { + "key": "advertising", + "description": "Layer 'Advertise' shows advertising=wall_painting with a fixed text, namely 'This is a wall painting' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "wall_painting" + }, + { + "key": "animated", + "description": "Layer 'Advertise' shows animated=no with a fixed text, namely 'Static, always shows the same message' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign&advertising!=board)", + "value": "no" + }, + { + "key": "animated", + "description": "Layer 'Advertise' shows animated=digital_display with a fixed text, namely 'This object has a built-in digital display to show prices or some other message' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign&advertising!=board)", + "value": "digital_display" + }, + { + "key": "animated", + "description": "Layer 'Advertise' shows animated=trivision_blades with a fixed text, namely 'Trivision - the billboard consists of many triangular prisms which regularly rotate' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign&advertising!=board)", + "value": "trivision_blades" + }, + { + "key": "animated", + "description": "Layer 'Advertise' shows animated=winding_posters with a fixed text, namely 'Scrolling posters' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign&advertising!=board)", + "value": "winding_posters" + }, + { + "key": "animated", + "description": "Layer 'Advertise' shows animated=revolving with a fixed text, namely 'Rotates on itself' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising!=screen&advertising!=flag&advertising!=tarp&advertising!=wall_painting&advertising!=sign&advertising!=board)", + "value": "revolving" + }, + { + "key": "luminous", + "description": "Layer 'Advertise' shows luminous=neon with a fixed text, namely 'This is a neon-tube light' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising!=screen)", + "value": "neon" + }, + { + "key": "lit", + "description": "Layer 'Advertise' shows lit=yes&luminous=yes with a fixed text, namely 'This object both emits light and is lighted by an external light source' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising!=screen)", + "value": "yes" + }, + { + "key": "luminous", + "description": "Layer 'Advertise' shows lit=yes&luminous=yes with a fixed text, namely 'This object both emits light and is lighted by an external light source' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising!=screen)", + "value": "yes" + }, + { + "key": "luminous", + "description": "Layer 'Advertise' shows luminous=yes with a fixed text, namely 'This object emits light' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising!=screen)", + "value": "yes" + }, + { + "key": "lit", + "description": "Layer 'Advertise' shows lit=yes with a fixed text, namely 'This object is lit externally, e.g. by a spotlight or other lights' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising!=screen)", + "value": "yes" + }, + { + "key": "lit", + "description": "Layer 'Advertise' shows lit=no&luminous=no with a fixed text, namely 'This object does not emit light and is not lighted by externally' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising!=screen)", + "value": "no" + }, + { + "key": "luminous", + "description": "Layer 'Advertise' shows lit=no&luminous=no with a fixed text, namely 'This object does not emit light and is not lighted by externally' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising!=screen)", + "value": "no" + }, + { + "key": "operator", + "description": "Layer 'Advertise' shows and asks freeform values for key 'operator' (in the MapComplete.osm.be theme 'Personal theme')" + }, + { + "key": "message", + "description": "Layer 'Advertise' shows message=commercial with a fixed text, namely 'Commercial message' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "commercial" + }, + { + "key": "message", + "description": "Layer 'Advertise' shows message=local with a fixed text, namely 'Local information' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "local" + }, + { + "key": "message", + "description": "Layer 'Advertise' shows message=safety with a fixed text, namely 'Securty information' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "safety" + }, + { + "key": "message", + "description": "Layer 'Advertise' shows message=political with a fixed text, namely 'Electoral advertising' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "political" + }, + { + "key": "message", + "description": "Layer 'Advertise' shows message=showbiz with a fixed text, namely 'Inormation related to theatre, concerts, ...' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "showbiz" + }, + { + "key": "message", + "description": "Layer 'Advertise' shows message=non_profit with a fixed text, namely 'Message from non-profit organizations' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "non_profit" + }, + { + "key": "message", + "description": "Layer 'Advertise' shows message=opinion with a fixed text, namely 'To expres your opinion' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "opinion" + }, + { + "key": "message", + "description": "Layer 'Advertise' shows message=religion with a fixed text, namely 'Religious message' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "religion" + }, + { + "key": "message", + "description": "Layer 'Advertise' shows message=funding with a fixed text, namely 'Funding sign' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "funding" + }, + { + "key": "information", + "description": "Layer 'Advertise' shows information=map with a fixed text, namely 'A map' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "value": "map" + }, + { + "key": "sides", + "description": "Layer 'Advertise' shows sides=1 with a fixed text, namely 'This object has advertisements on a single side' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising=poster_box|advertising=screen|advertising=billboard)", + "value": "1" + }, + { + "key": "sides", + "description": "Layer 'Advertise' shows sides=2 with a fixed text, namely 'This object has advertisements on both sides' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising=poster_box|advertising=screen|advertising=billboard)", + "value": "2" + }, + { + "key": "ref", + "description": "Layer 'Advertise' shows and asks freeform values for key 'ref' (in the MapComplete.osm.be theme 'Personal theme') (This is only shown if advertising!=sign)" + }, { "key": "emergency", "description": "The MapComplete theme Personal theme has a layer Map of ambulance stations showing features with this tag", @@ -5378,6 +5585,14 @@ "key": "name", "description": "Layer 'Dentist' shows and asks freeform values for key 'name' (in the MapComplete.osm.be theme 'Personal theme')" }, + { + "key": "camera:direction", + "description": "The MapComplete theme Personal theme has a layer Direction visualization showing features with this tag" + }, + { + "key": "direction", + "description": "The MapComplete theme Personal theme has a layer Direction visualization showing features with this tag" + }, { "key": "amenity", "description": "The MapComplete theme Personal theme has a layer Doctors showing features with this tag", @@ -9357,17 +9572,17 @@ }, { "key": "recycling:printer_cartridges", - "description": "Layer 'Recycling' shows recycling:printer_cartridges=yes with a fixed text, namely 'Scrap metal can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "description": "Layer 'Recycling' shows recycling:printer_cartridges=yes with a fixed text, namely 'Printer cartridges can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", "value": "yes" }, { "key": "recycling:scrap_metal", - "description": "Layer 'Recycling' shows recycling:scrap_metal=yes with a fixed text, namely 'Shoes can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "description": "Layer 'Recycling' shows recycling:scrap_metal=yes with a fixed text, namely 'Scrap metal can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", "value": "yes" }, { "key": "recycling:shoes", - "description": "Layer 'Recycling' shows recycling:shoes=yes with a fixed text, namely 'Small electrical appliances can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "description": "Layer 'Recycling' shows recycling:shoes=yes with a fixed text, namely 'Shoes can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", "value": "yes" }, { @@ -9377,12 +9592,12 @@ }, { "key": "recycling:small_electrical_appliances", - "description": "Layer 'Recycling' shows recycling:small_electrical_appliances=yes with a fixed text, namely 'Needles can be recycled here' (in the MapComplete.osm.be theme 'Personal theme')", + "description": "Layer 'Recycling' shows recycling:small_electrical_appliances=yes with a fixed text, namely 'Small electrical appliances can be recycled here' (in the MapComplete.osm.be theme 'Personal theme')", "value": "yes" }, { "key": "recycling:needles", - "description": "Layer 'Recycling' shows recycling:needles=yes with a fixed text, namely 'Residual waste can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", + "description": "Layer 'Recycling' shows recycling:needles=yes with a fixed text, namely 'Needles can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Personal theme')", "value": "yes" }, { diff --git a/Docs/TagInfo/mapcomplete_surveillance.json b/Docs/TagInfo/mapcomplete_surveillance.json index de44ce25c9..f51e451a14 100644 --- a/Docs/TagInfo/mapcomplete_surveillance.json +++ b/Docs/TagInfo/mapcomplete_surveillance.json @@ -10,6 +10,14 @@ "contact_email": "pietervdvn@posteo.net" }, "tags": [ + { + "key": "camera:direction", + "description": "The MapComplete theme Surveillance under Surveillance has a layer Direction visualization showing features with this tag" + }, + { + "key": "direction", + "description": "The MapComplete theme Surveillance under Surveillance has a layer Direction visualization showing features with this tag" + }, { "key": "man_made", "description": "The MapComplete theme Surveillance under Surveillance has a layer Surveillance camera's showing features with this tag", diff --git a/Docs/TagInfo/mapcomplete_uk_addresses.json b/Docs/TagInfo/mapcomplete_uk_addresses.json deleted file mode 100644 index 92774e2f19..0000000000 --- a/Docs/TagInfo/mapcomplete_uk_addresses.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "data_format": 1, - "project": { - "name": "MapComplete UK Addresses", - "description": "Help to build an open dataset of UK addresses", - "project_url": "https://mapcomplete.osm.be/uk_addresses", - "doc_url": "https://github.com/pietervdvn/MapComplete/tree/master/assets/themes/", - "icon_url": "https://mapcomplete.osm.be/assets/themes/uk_addresses/housenumber_unknown.svg", - "contact_name": "Pieter Vander Vennet, Pieter Vander Vennet, Rob Nickerson, Russ Garrett", - "contact_email": "pietervdvn@posteo.net" - }, - "tags": [ - { - "key": "inspireid", - "description": "The MapComplete theme UK Addresses has a layer Addresses to check showing features with this tag" - }, - { - "key": "addr:housenumber", - "description": "The MapComplete theme UK Addresses has a layer Known addresses in OSM showing features with this tag" - }, - { - "key": "addr:street", - "description": "The MapComplete theme UK Addresses has a layer Known addresses in OSM showing features with this tag" - }, - { - "key": "addr:housenumber", - "description": "Layer 'Known addresses in OSM' shows and asks freeform values for key 'addr:housenumber' (in the MapComplete.osm.be theme 'UK Addresses')" - }, - { - "key": "nohousenumber", - "description": "Layer 'Known addresses in OSM' shows nohousenumber=yes with a fixed text, namely 'This building has no house number' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'UK Addresses')", - "value": "yes" - }, - { - "key": "addr:street", - "description": "Layer 'Known addresses in OSM' shows and asks freeform values for key 'addr:street' (in the MapComplete.osm.be theme 'UK Addresses')" - } - ] -} \ No newline at end of file diff --git a/Docs/TagInfo/mapcomplete_walls_and_buildings.json b/Docs/TagInfo/mapcomplete_walls_and_buildings.json deleted file mode 100644 index efffff8ae2..0000000000 --- a/Docs/TagInfo/mapcomplete_walls_and_buildings.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "data_format": 1, - "project": { - "name": "MapComplete Walls and buildings", - "description": "Special builtin layer providing all walls and buildings", - "project_url": "https://mapcomplete.osm.be/walls_and_buildings", - "doc_url": "https://github.com/pietervdvn/MapComplete/tree/master/assets/themes/", - "icon_url": "https://mapcomplete.osm.be/assets/layers/walls_and_buildings/walls_and_buildings.png", - "contact_name": "Pieter Vander Vennet, MapComplete", - "contact_email": "pietervdvn@posteo.net" - }, - "tags": [ - { - "key": "highway", - "description": "The MapComplete theme Walls and buildings has a layer Pedestrian paths showing features with this tag", - "value": "footway" - }, - { - "key": "highway", - "description": "The MapComplete theme Walls and buildings has a layer Pedestrian paths showing features with this tag", - "value": "path" - }, - { - "key": "highway", - "description": "The MapComplete theme Walls and buildings has a layer Pedestrian paths showing features with this tag", - "value": "corridor" - }, - { - "key": "highway", - "description": "The MapComplete theme Walls and buildings has a layer Pedestrian paths showing features with this tag", - "value": "steps" - }, - { - "key": "entrance", - "description": "The MapComplete theme Walls and buildings has a layer Entrance showing features with this tag" - }, - { - "key": "indoor", - "description": "The MapComplete theme Walls and buildings has a layer Entrance showing features with this tag", - "value": "door" - }, - { - "key": "image", - "description": "The layer 'Entrance allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" - }, - { - "key": "mapillary", - "description": "The layer 'Entrance allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" - }, - { - "key": "wikidata", - "description": "The layer 'Entrance allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" - }, - { - "key": "wikipedia", - "description": "The layer 'Entrance allows to upload images and adds them under the 'image'-tag (and image:0, image:1, ... for multiple images). Furhtermore, this layer shows images based on the keys image, wikidata, wikipedia, wikimedia_commons and mapillary" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=yes with a fixed text, namely 'No specific entrance type is known' (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "yes" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows indoor=door with a fixed text, namely 'This is an indoor door, separating a room or a corridor within a single building' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings') Picking this answer will delete the key entrance.", - "value": "" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows indoor=door with a fixed text, namely 'This is an indoor door, separating a room or a corridor within a single building' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "door" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=main with a fixed text, namely 'This is the main entrance' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=main with a fixed text, namely 'This is the main entrance' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "main" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=secondary with a fixed text, namely 'This is a secondary entrance' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=secondary with a fixed text, namely 'This is a secondary entrance' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "secondary" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=service with a fixed text, namely 'This is a service entrance - normally only used for employees, delivery, …' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=service with a fixed text, namely 'This is a service entrance - normally only used for employees, delivery, …' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "service" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=exit with a fixed text, namely 'This is an exit where one can not enter' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=exit with a fixed text, namely 'This is an exit where one can not enter' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "exit" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=entrance with a fixed text, namely 'This is an entrance where one can only enter (but not exit)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=entrance with a fixed text, namely 'This is an entrance where one can only enter (but not exit)' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "entrance" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=emergency with a fixed text, namely 'This is emergency exit' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=emergency with a fixed text, namely 'This is emergency exit' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "emergency" - }, - { - "key": "indoor", - "description": "Layer 'Entrance' shows entrance=home with a fixed text, namely 'This is the entrance to a private home' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings') Picking this answer will delete the key indoor.", - "value": "" - }, - { - "key": "entrance", - "description": "Layer 'Entrance' shows entrance=home with a fixed text, namely 'This is the entrance to a private home' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "home" - }, - { - "key": "door", - "description": "Layer 'Entrance' shows door=yes with a fixed text, namely 'The door type is not known' (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "yes" - }, - { - "key": "door", - "description": "Layer 'Entrance' shows door=hinged with a fixed text, namely 'A classical, hinged door supported by joints' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "hinged" - }, - { - "key": "door", - "description": "Layer 'Entrance' shows door=revolving with a fixed text, namely 'A revolving door which hangs on a central shaft, rotating within a cylindrical enclosure' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "revolving" - }, - { - "key": "door", - "description": "Layer 'Entrance' shows door=sliding with a fixed text, namely 'A sliding door where the door slides sidewards, typically parallel with a wall' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "sliding" - }, - { - "key": "door", - "description": "Layer 'Entrance' shows door=overhead with a fixed text, namely 'A door which rolls from overhead, typically seen for garages' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "overhead" - }, - { - "key": "door", - "description": "Layer 'Entrance' shows door=no with a fixed text, namely 'This is an entrance without a physical door' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "no" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=yes with a fixed text, namely 'This is an automatic door' (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "yes" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=no with a fixed text, namely 'This door is not automated' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "no" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=motion with a fixed text, namely 'This door will open automatically when motion is detected' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "motion" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=floor with a fixed text, namely 'This door will open automatically when a sensor in the floor is triggered' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "floor" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=button with a fixed text, namely 'This door will open automatically when a button is pressed' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "button" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=slowdown_button with a fixed text, namely 'This door revolves automatically all the time, but has a button to slow it down, e.g. for wheelchair users' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "slowdown_button" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=continuous with a fixed text, namely 'This door revolves automatically all the time' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "continuous" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=serviced_on_button_press with a fixed text, namely 'This door will be opened by staff when requested by pressing a button' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "serviced_on_button_press" - }, - { - "key": "automatic_door", - "description": "Layer 'Entrance' shows automatic_door=serviced_on_request with a fixed text, namely 'This door will be opened by staff when requested' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Walls and buildings')", - "value": "serviced_on_request" - }, - { - "key": "width", - "description": "Layer 'Entrance' shows and asks freeform values for key 'width' (in the MapComplete.osm.be theme 'Walls and buildings')" - } - ] -} \ No newline at end of file diff --git a/Docs/TagInfo/mapcomplete_waste.json b/Docs/TagInfo/mapcomplete_waste.json index c1ecabf282..c747057436 100644 --- a/Docs/TagInfo/mapcomplete_waste.json +++ b/Docs/TagInfo/mapcomplete_waste.json @@ -238,17 +238,17 @@ }, { "key": "recycling:printer_cartridges", - "description": "Layer 'Recycling' shows recycling:printer_cartridges=yes with a fixed text, namely 'Scrap metal can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste')", + "description": "Layer 'Recycling' shows recycling:printer_cartridges=yes with a fixed text, namely 'Printer cartridges can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste')", "value": "yes" }, { "key": "recycling:scrap_metal", - "description": "Layer 'Recycling' shows recycling:scrap_metal=yes with a fixed text, namely 'Shoes can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste')", + "description": "Layer 'Recycling' shows recycling:scrap_metal=yes with a fixed text, namely 'Scrap metal can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste')", "value": "yes" }, { "key": "recycling:shoes", - "description": "Layer 'Recycling' shows recycling:shoes=yes with a fixed text, namely 'Small electrical appliances can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste')", + "description": "Layer 'Recycling' shows recycling:shoes=yes with a fixed text, namely 'Shoes can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste')", "value": "yes" }, { @@ -258,12 +258,12 @@ }, { "key": "recycling:small_electrical_appliances", - "description": "Layer 'Recycling' shows recycling:small_electrical_appliances=yes with a fixed text, namely 'Needles can be recycled here' (in the MapComplete.osm.be theme 'Waste')", + "description": "Layer 'Recycling' shows recycling:small_electrical_appliances=yes with a fixed text, namely 'Small electrical appliances can be recycled here' (in the MapComplete.osm.be theme 'Waste')", "value": "yes" }, { "key": "recycling:needles", - "description": "Layer 'Recycling' shows recycling:needles=yes with a fixed text, namely 'Residual waste can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste')", + "description": "Layer 'Recycling' shows recycling:needles=yes with a fixed text, namely 'Needles can be recycled here' and allows to pick this as a default answer (in the MapComplete.osm.be theme 'Waste')", "value": "yes" }, { diff --git a/Docs/URL_Parameters.md b/Docs/URL_Parameters.md index c9c10727f1..455a7cdd57 100644 --- a/Docs/URL_Parameters.md +++ b/Docs/URL_Parameters.md @@ -11,7 +11,6 @@ - [What is a URL parameter?](#what-is-a-url-parameter) - [language](#language) - [fs-translation-mode](#fs-translation-mode) - - [tab](#tab) - [fs-userbadge](#fs-userbadge) - [fs-search](#fs-search) - [fs-background](#fs-background) @@ -78,13 +77,6 @@ Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case. - tab ------ - - The tab that is shown in the welcome-message. The default value is _0_ - - - fs-userbadge -------------- diff --git a/Docs/wikiIndex.txt b/Docs/wikiIndex.txt index e69de29bb2..c46c0991ce 100644 --- a/Docs/wikiIndex.txt +++ b/Docs/wikiIndex.txt @@ -0,0 +1,499 @@ +{|class="wikitable sortable" +! Name, link !! Genre !! Covered region !! Language !! Description !! Free materials !! Image +|- +{{service_item +|name= [https://mapcomplete.osm.be/personal personal] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:es|en}}, {{#language:ca|en}}, {{#language:gl|en}}, {{#language:fr|en}}, {{#language:de|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:ru|en}}, {{#language:it|en}}, {{#language:da|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: Create a personal theme based on all the available layers of all themes +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, personal +}} +{{service_item +|name= [https://mapcomplete.osm.be/cyclofix cyclofix] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:gl|en}}, {{#language:de|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: The goal of this map is to present cyclists with an easy-to-use solution to find the appropriate infrastructure for their needs +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, cyclofix +}} +{{service_item +|name= [https://mapcomplete.osm.be/waste waste] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:it|en}}, {{#language:ca|en}}, {{#language:da|en}}, {{#language:es|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: Map showing waste baskets and recycling facilities +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, waste +}} +{{service_item +|name= [https://mapcomplete.osm.be/etymology etymology] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:fr|en}}, {{#language:es|en}}, {{#language:ca|en}}, {{#language:da|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: What is the origin of a toponym? +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, etymology +}} +{{service_item +|name= [https://mapcomplete.osm.be/food food] +|region= Worldwide +|lang= {{#language:nl|en}}, {{#language:en|en}}, {{#language:de|en}}, {{#language:es|en}}, {{#language:nb_NO|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: Restaurants and fast food +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, food +}} +{{service_item +|name= [https://mapcomplete.osm.be/cafes_and_pubs cafes_and_pubs] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:nb_NO|en}}, {{#language:pa_PK|en}}, {{#language:cs|en}}, {{#language:it|en}} +|descr= A MapComplete theme: Coffeehouses, pubs and bars +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, cafes_and_pubs +}} +{{service_item +|name= [https://mapcomplete.osm.be/playgrounds playgrounds] +|region= Worldwide +|lang= {{#language:nl|en}}, {{#language:en|en}}, {{#language:fr|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:ru|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:nb_NO|en}}, {{#language:id|en}}, {{#language:hu|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: A map with playgrounds +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, playgrounds +}} +{{service_item +|name= [https://mapcomplete.osm.be/hailhydrant hailhydrant] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:ru|en}}, {{#language:fr|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:ca|en}}, {{#language:nl|en}}, {{#language:da|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: Map to show hydrants, extinguishers, fire stations, and ambulance stations. +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, hailhydrant +}} +{{service_item +|name= [https://mapcomplete.osm.be/toilets toilets] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:nl|en}}, {{#language:ru|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:pl|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:nb_NO|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: A map of public toilets +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, toilets +}} +{{service_item +|name= [https://mapcomplete.osm.be/aed aed] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:id|en}}, {{#language:it|en}}, {{#language:ru|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:sv|en}}, {{#language:pl|en}}, {{#language:pt_BR|en}}, {{#language:nb_NO|en}}, {{#language:hu|en}}, {{#language:sl|en}}, {{#language:zh_Hans|en}}, {{#language:da|en}}, {{#language:fil|en}}, {{#language:cs|en}}, {{#language:zgh|en}} +|descr= A MapComplete theme: On this map, one can find and mark nearby defibrillators +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, aed +}} +{{service_item +|name= [https://mapcomplete.osm.be/bookcases bookcases] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:ru|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: A public bookcase is a small streetside cabinet, box, old phone booth or some other objects where books are stored +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, bookcases +}} +{{service_item +|name= [https://mapcomplete.osm.be/advertising advertising] +|region= Worldwide +|lang= {{#language:ca|en}}, {{#language:es|en}}, {{#language:en|en}}, {{#language:de|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: Where I can find advertising features? +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, advertising +}} +{{service_item +|name= [https://mapcomplete.osm.be/artwork artwork] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:de|en}}, {{#language:id|en}}, {{#language:it|en}}, {{#language:ru|en}}, {{#language:es|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:nb_NO|en}}, {{#language:hu|en}}, {{#language:pl|en}}, {{#language:ca|en}}, {{#language:zh_Hans|en}}, {{#language:fil|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:zgh|en}} +|descr= A MapComplete theme: An open map of statues, busts, graffitis and other artwork all over the world +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, artwork +}} +{{service_item +|name= [https://mapcomplete.osm.be/atm atm] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:nl|en}}, {{#language:ca|en}}, {{#language:cs|en}}, {{#language:nb_NO|en}}, {{#language:es|en}}, {{#language:id|en}} +|descr= A MapComplete theme: This map shows ATMs to withdraw or deposit money +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, atm +}} +{{service_item +|name= [https://mapcomplete.osm.be/benches benches] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:nl|en}}, {{#language:it|en}}, {{#language:ru|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:nb_NO|en}}, {{#language:pt_BR|en}}, {{#language:hu|en}}, {{#language:id|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:zh_Hans|en}}, {{#language:da|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: A map of benches +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, benches +}} +{{service_item +|name= [https://mapcomplete.osm.be/bicycle_rental bicycle_rental] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:id|en}}, {{#language:fr|en}}, {{#language:es|en}}, {{#language:nb_NO|en}}, {{#language:ca|en}}, {{#language:da|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: A map with bicycle rental stations and bicycle rental shops +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, bicycle_rental +}} +{{service_item +|name= [https://mapcomplete.osm.be/bicyclelib bicyclelib] +|region= Worldwide +|lang= {{#language:nl|en}}, {{#language:en|en}}, {{#language:it|en}}, {{#language:ru|en}}, {{#language:ja|en}}, {{#language:fr|en}}, {{#language:zh_Hant|en}}, {{#language:de|en}}, {{#language:hu|en}}, {{#language:nb_NO|en}}, {{#language:ca|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:es|en}} +|descr= A MapComplete theme: A bicycle library is a place where bicycles can be lent, often for a small yearly fee +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, bicyclelib +}} +{{service_item +|name= [https://mapcomplete.osm.be/binoculars binoculars] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:nb_NO|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:fr|en}}, {{#language:es|en}}, {{#language:ca|en}}, {{#language:da|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: A map with fixed binoculars +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, binoculars +}} +{{service_item +|name= [https://mapcomplete.osm.be/blind_osm blind_osm] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:ca|en}}, {{#language:cs|en}}, {{#language:nb_NO|en}}, {{#language:es|en}} +|descr= A MapComplete theme: Help to map features relevant for the blind +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, blind_osm +}} +{{service_item +|name= [https://mapcomplete.osm.be/campersite campersite] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:it|en}}, {{#language:ru|en}}, {{#language:ja|en}}, {{#language:fr|en}}, {{#language:zh_Hant|en}}, {{#language:nl|en}}, {{#language:pt_BR|en}}, {{#language:de|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: Find sites to spend the night with your camper +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, campersite +}} +{{service_item +|name= [https://mapcomplete.osm.be/charging_stations charging_stations] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:it|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:nb_NO|en}}, {{#language:ru|en}}, {{#language:hu|en}}, {{#language:fr|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: A worldwide map of charging stations +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, charging_stations +}} +{{service_item +|name= [https://mapcomplete.osm.be/climbing climbing] +|region= Worldwide +|lang= {{#language:nl|en}}, {{#language:de|en}}, {{#language:en|en}}, {{#language:ru|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:fr|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:nb_NO|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:ca|en}}, {{#language:es|en}} +|descr= A MapComplete theme: On this map you will find various climbing opportunities such as climbing gyms, bouldering halls and rocks in nature +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, climbing +}} +{{service_item +|name= [https://mapcomplete.osm.be/clock clock] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:ca|en}}, {{#language:de|en}}, {{#language:es|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: Map showing all public clocks +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, clock +}} +{{service_item +|name= [https://mapcomplete.osm.be/cycle_infra cycle_infra] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:nb_NO|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:ca|en}}, {{#language:da|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: A map where you can view and edit things related to the bicycle infrastructure. +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, cycle_infra +}} +{{service_item +|name= [https://mapcomplete.osm.be/cyclestreets cyclestreets] +|region= Worldwide +|lang= {{#language:nl|en}}, {{#language:en|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:nb_NO|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: A map of cyclestreets +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, cyclestreets +}} +{{service_item +|name= [https://mapcomplete.osm.be/drinking_water drinking_water] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:it|en}}, {{#language:ru|en}}, {{#language:de|en}}, {{#language:nb_NO|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:ca|en}}, {{#language:da|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: On this map, publicly accessible drinking water spots are shown and can be easily added +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, drinking_water +}} +{{service_item +|name= [https://mapcomplete.osm.be/education education] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:ca|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:es|en}} +|descr= A MapComplete theme: On this map, you'll find information about all types of schools and education and can easily add more information +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, education +}} +{{service_item +|name= [https://mapcomplete.osm.be/facadegardens facadegardens] +|region= Worldwide +|lang= {{#language:nl|en}}, {{#language:en|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:it|en}}, {{#language:fr|en}}, {{#language:de|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: This map shows facade gardens with pictures and useful info about orientation, sunshine and plant types. +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, facadegardens +}} +{{service_item +|name= [https://mapcomplete.osm.be/fritures fritures] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:ca|en}}, {{#language:es|en}} +|descr= A MapComplete theme: On this map, you'll find your favourite fries shop! +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, fritures +}} +{{service_item +|name= [https://mapcomplete.osm.be/ghostbikes ghostbikes] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:fr|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:es|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: A +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, ghostbikes +}} +{{service_item +|name= [https://mapcomplete.osm.be/hackerspaces hackerspaces] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:es|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: A map of hackerspaces +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, hackerspaces +}} +{{service_item +|name= [https://mapcomplete.osm.be/healthcare healthcare] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:ca|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:nl|en}}, {{#language:cs|en}}, {{#language:es|en}} +|descr= A MapComplete theme: On this map, various healthcare related items are shown +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, healthcare +}} +{{service_item +|name= [https://mapcomplete.osm.be/hotels hotels] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:da|en}}, {{#language:nb_NO|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:cs|en}}, {{#language:es|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: On this map, you'll find hotels in your area +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, hotels +}} +{{service_item +|name= [https://mapcomplete.osm.be/indoors indoors] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:nl|en}}, {{#language:cs|en}}, {{#language:nb_NO|en}}, {{#language:es|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: On this map, publicly accessible indoor places are shown +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, indoors +}} +{{service_item +|name= [https://mapcomplete.osm.be/kerbs_and_crossings kerbs_and_crossings] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:nb_NO|en}}, {{#language:es|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: A map showing kerbs and crossings +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, kerbs_and_crossings +}} +{{service_item +|name= [https://mapcomplete.osm.be/maps maps] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: This theme shows all (touristic) maps that OpenStreetMap knows of +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, maps +}} +{{service_item +|name= [https://mapcomplete.osm.be/maxspeed maxspeed] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:nl|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:es|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: This map shows the legally allowed maximum speed on every road. +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, maxspeed +}} +{{service_item +|name= [https://mapcomplete.osm.be/nature nature] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: A map for nature lovers, with interesting POI's +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, nature +}} +{{service_item +|name= [https://mapcomplete.osm.be/notes notes] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:hu|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: A note is a pin on the map with some text to indicate something wrong +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, notes +}} +{{service_item +|name= [https://mapcomplete.osm.be/observation_towers observation_towers] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:nb_NO|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: Publicly accessible towers to enjoy the view +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, observation_towers +}} +{{service_item +|name= [https://mapcomplete.osm.be/onwheels onwheels] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:nl|en}}, {{#language:da|en}}, {{#language:cs|en}}, {{#language:es|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: On this map, publicly weelchair accessible places are shown and can be easily added +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, onwheels +}} +{{service_item +|name= [https://mapcomplete.osm.be/openwindpowermap openwindpowermap] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:fr|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:nl|en}}, {{#language:da|en}}, {{#language:nb_NO|en}}, {{#language:cs|en}}, {{#language:ca|en}} +|descr= A MapComplete theme: A map for showing and editing wind turbines +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, openwindpowermap +}} +{{service_item +|name= [https://mapcomplete.osm.be/osm_community_index osm_community_index] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:es|en}}, {{#language:ca|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: An index of community resources for OpenStreetMap. +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, osm_community_index +}} +{{service_item +|name= [https://mapcomplete.osm.be/parkings parkings] +|region= Worldwide +|lang= {{#language:nl|en}}, {{#language:en|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:nb_NO|en}}, {{#language:zh_Hant|en}}, {{#language:id|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: This map shows different parking spots +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, parkings +}} +{{service_item +|name= [https://mapcomplete.osm.be/pets pets] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:da|en}}, {{#language:de|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: On this map, you'll find various interesting places for you pets: veterinarians, dog parks, pet shops, dog-friendly restaurants, +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, pets +}} +{{service_item +|name= [https://mapcomplete.osm.be/postboxes postboxes] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:nb_NO|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: A map showing postboxes and post offices +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, postboxes +}} +{{service_item +|name= [https://mapcomplete.osm.be/rainbow_crossings rainbow_crossings] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:nl|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: On this map, rainbow-painted pedestrian crossings are shown and can be easily added +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, rainbow_crossings +}} +{{service_item +|name= [https://mapcomplete.osm.be/shops shops] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:fr|en}}, {{#language:ja|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:nl|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: An editable map with basic shop information +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, shops +}} +{{service_item +|name= [https://mapcomplete.osm.be/sport_pitches sport_pitches] +|region= Worldwide +|lang= {{#language:nl|en}}, {{#language:fr|en}}, {{#language:en|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:ru|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: A map showing sport pitches +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, sport_pitches +}} +{{service_item +|name= [https://mapcomplete.osm.be/sports sports] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: Map showing sport facilities. +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, sports +}} +{{service_item +|name= [https://mapcomplete.osm.be/street_lighting street_lighting] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:de|en}}, {{#language:es|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:nb_NO|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: On this map you can find everything about street lighting +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, street_lighting +}} +{{service_item +|name= [https://mapcomplete.osm.be/surveillance surveillance] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:fr|en}}, {{#language:pl|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:hu|en}}, {{#language:da|en}}, {{#language:nb_NO|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: Surveillance cameras and other means of surveillance +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, surveillance +}} +{{service_item +|name= [https://mapcomplete.osm.be/transit transit] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:de|en}}, {{#language:fr|en}}, {{#language:da|en}}, {{#language:nl|en}}, {{#language:nb_NO|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: Plan your trip with the help of the public transport system +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, transit +}} +{{service_item +|name= [https://mapcomplete.osm.be/trees trees] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:fr|en}}, {{#language:it|en}}, {{#language:ja|en}}, {{#language:zh_Hant|en}}, {{#language:ru|en}}, {{#language:pl|en}}, {{#language:de|en}}, {{#language:nb_NO|en}}, {{#language:hu|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:da|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: Map all the trees +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, trees +}} +{{service_item +|name= [https://mapcomplete.osm.be/waste_basket waste_basket] +|region= Worldwide +|lang= {{#language:en|en}}, {{#language:nl|en}}, {{#language:de|en}}, {{#language:it|en}}, {{#language:zh_Hant|en}}, {{#language:hu|en}}, {{#language:fr|en}}, {{#language:nb_NO|en}}, {{#language:da|en}}, {{#language:ca|en}}, {{#language:es|en}}, {{#language:cs|en}} +|descr= A MapComplete theme: A map with waste baskets +|material= {{yes|[https://mapcomplete.osm.be/ Yes]}} +|image= MapComplete_Screenshot.png +|genre= POI, editor, waste_basket +}} +|} \ No newline at end of file diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index bedbf65de5..a8488b053f 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -480,7 +480,7 @@ export default class LayerConfig extends WithContextLoader { ) } - if (this.source.geojsonSource !== undefined) { + if (this.source?.geojsonSource !== undefined) { extraProps.push( new Combine([ Utils.runningFromConsole @@ -530,8 +530,8 @@ export default class LayerConfig extends WithContextLoader { ) } - let neededTags: TagsFilter[] = [this.source.osmTags] - if (this.source.osmTags["and"] !== undefined) { + let neededTags: TagsFilter[] = Utils.NoNull([this.source?.osmTags]) + if (this.source?.osmTags["and"] !== undefined) { neededTags = this.source.osmTags["and"] } diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index 93650590e4..a5b27c22d2 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -36,20 +36,11 @@ export default class DefaultGUI { } private SetupUIElements() { - const guiState = this.guiState - const extraLink = Toggle.If( state.featureSwitchExtraLinkEnabled, () => new ExtraLinkButton(state, state.layoutToUse.extraLink) ) - new ScrollableFullScreen( - () => Translations.t.general.attribution.attributionTitle, - () => new CopyrightPanel(state), - "copyright", - guiState.copyrightViewIsOpened - ) - new Combine([extraLink]).SetClass("flex flex-col").AttachTo("top-left") new Combine([ @@ -64,6 +55,7 @@ export default class DefaultGUI { .SetClass("flex items-center justify-center normal-background h-full") .AttachTo("on-small-screen") + const guiState = this.guiState new LeftControls(state, guiState).AttachTo("bottom-left") new RightControls(state, this.geolocationHandler).AttachTo("bottom-right") diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index cc0dcdbfd5..fb19e82bbf 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -44,25 +44,21 @@ export default class FeatureInfoBox extends ScrollableFullScreen { public static GenerateContent( tags: UIEventSource, - layerConfig: LayerConfig, - state: FeaturePipelineState, - showAllQuestions?: Store + layerConfig: LayerConfig ): BaseUIElement { return new Toggle( new Combine([ Svg.delete_icon_svg().SetClass("w-8 h-8"), Translations.t.delete.isDeleted, ]).SetClass("flex justify-center font-bold items-center"), - FeatureInfoBox.GenerateMainContent(tags, layerConfig, state, showAllQuestions), + FeatureInfoBox.GenerateMainContent(tags, layerConfig), tags.map((t) => t["_deleted"] == "yes") ) } private static GenerateMainContent( tags: UIEventSource, - layerConfig: LayerConfig, - state: FeaturePipelineState + layerConfig: LayerConfig ): BaseUIElement { - let questionBoxes: Map = new Map() const t = Translations.t.general const withQuestion = layerConfig.tagRenderings.filter( diff --git a/package.json b/package.json index ea5c8a9c16..5926bd549e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "generate:translations": "vite-node scripts/generateTranslations.ts", "reset:translations": "vite-node scripts/generateTranslations.ts -- --ignore-weblate", "generate:layouts": "vite-node scripts/generateLayouts.ts", - "generate:docs": "vite-node scripts/generateDocs.ts && vite-node scripts/generateTaginfoProjectFiles.ts", + "generate:docs": "rm -rf Docs/Themes/* && rm -rf Docs/Layers/* && rm -rf Docs/TagInfo && mkdir Docs/TagInfo && vite-node scripts/generateDocs.ts && vite-node scripts/generateTaginfoProjectFiles.ts", "generate:cache:speelplekken": "npm run generate:layeroverview && vite-node scripts/generateCache.ts -- speelplekken 14 ../MapComplete-data/speelplekken_cache/ 51.20 4.35 51.09 4.56", "generate:cache:natuurpunt": "npm run generate:layeroverview && vite-node scripts/generateCache.ts -- natuurpunt 12 ../MapComplete-data/natuurpunt_cache/ 50.40 2.1 51.54 6.4 --generate-point-overview nature_reserve,visitor_information_centre", "generate:layeroverview": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts", diff --git a/scripts/generateDocs.ts b/scripts/generateDocs.ts index 0399c4bf47..ed2e0c639e 100644 --- a/scripts/generateDocs.ts +++ b/scripts/generateDocs.ts @@ -1,26 +1,20 @@ import Combine from "../UI/Base/Combine" import BaseUIElement from "../UI/BaseUIElement" -import { existsSync, mkdirSync, writeFileSync } from "fs" +import { existsSync, mkdirSync, writeFile, writeFileSync } from "fs" import { AllKnownLayouts } from "../Customizations/AllKnownLayouts" import TableOfContents from "../UI/Base/TableOfContents" import SimpleMetaTaggers from "../Logic/SimpleMetaTagger" -import ValidatedTextField from "../UI/Input/ValidatedTextField" import SpecialVisualizations from "../UI/SpecialVisualizations" import { ExtraFunctions } from "../Logic/ExtraFunctions" import Title from "../UI/Base/Title" -import Minimap from "../UI/Base/Minimap" import QueryParameterDocumentation from "../UI/QueryParameterDocumentation" import ScriptUtils from "./ScriptUtils" import List from "../UI/Base/List" import SharedTagRenderings from "../Customizations/SharedTagRenderings" -import { writeFile } from "fs" import Translations from "../UI/i18n/Translations" import themeOverview from "../assets/generated/theme_overview.json" -import DefaultGUI from "../UI/DefaultGUI" -import FeaturePipelineState from "../Logic/State/FeaturePipelineState" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" import bookcases from "../assets/generated/themes/bookcases.json" -import { DefaultGuiState } from "../UI/DefaultGuiState" import fakedom from "fake-dom" import Hotkeys from "../UI/Base/Hotkeys" import { QueryParameters } from "../Logic/Web/QueryParameters" @@ -29,6 +23,9 @@ import Constants from "../Models/Constants" import LayerConfig from "../Models/ThemeConfig/LayerConfig" import DependencyCalculator from "../Models/ThemeConfig/DependencyCalculator" import { AllSharedLayers } from "../Customizations/AllSharedLayers" +import ThemeViewState from "../Models/ThemeViewState" +import Validators from "../UI/InputElement/Validators" + function WriteFile( filename, html: BaseUIElement, @@ -159,8 +156,8 @@ function GenLayerOverviewText(): BaseUIElement { themesPerLayer.get(l.id), layerIsNeededBy, DependencyCalculator.getLayerDependencies(l), - Constants.added_by_default.indexOf(l.id) >= 0, - Constants.no_include.indexOf(l.id) < 0 + Constants.added_by_default.indexOf(l.id) >= 0, + Constants.no_include.indexOf(l.id) < 0 ) ), new Title("Normal layers", 1), @@ -310,7 +307,7 @@ ScriptUtils.fixUtils() generateWikipage() GenOverviewsForSingleLayer((layer, element, inlineSource) => { - console.log("Exporting ", layer.id) + ScriptUtils.erasableLog("Exporting layer documentation for", layer.id) if (!existsSync("./Docs/Layers")) { mkdirSync("./Docs/Layers") } @@ -322,6 +319,9 @@ GenOverviewsForSingleLayer((layer, element, inlineSource) => { }) Array.from(AllKnownLayouts.allKnownLayouts.values()).map((theme) => { + if (!existsSync("./Docs/Themes")) { + mkdirSync("./Docs/Themes") + } const docs = GenerateDocumentationForTheme(theme) WriteFile( "./Docs/Themes/" + theme.id + ".md", @@ -342,8 +342,8 @@ WriteFile( ]).SetClass("flex-col"), ["Logic/SimpleMetaTagger.ts", "Logic/ExtraFunctions.ts"] ) -WriteFile("./Docs/SpecialInputElements.md", ValidatedTextField.HelpText(), [ - "UI/Input/ValidatedTextField.ts", +WriteFile("./Docs/SpecialInputElements.md", Validators.HelpText(), [ + "UI/InputElement/Validators.ts", ]) WriteFile("./Docs/BuiltinLayers.md", GenLayerOverviewText(), ["Customizations/AllKnownLayouts.ts"]) WriteFile("./Docs/BuiltinQuestions.md", SharedTagRenderings.HelpText(), [ @@ -397,11 +397,6 @@ WriteFile("./Docs/BuiltinQuestions.md", SharedTagRenderings.HelpText(), [ WriteFile("./Docs/BuiltinIndex.md", docs, ["assets/layers/*.json"]) } -Minimap.createMiniMap = (_) => { - console.log("Not creating a minimap, it is disabled") - return undefined -} - WriteFile("./Docs/URL_Parameters.md", QueryParameterDocumentation.GenerateQueryParameterDocs(), [ "Logic/Web/QueryParameters.ts", "UI/QueryParameterDocumentation.ts", @@ -415,10 +410,8 @@ QueryParameters.GetQueryParameter( "The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'" ) -new DefaultGUI( - new FeaturePipelineState(new LayoutConfig(bookcases)), - new DefaultGuiState() -).setup() - -WriteFile("./Docs/Hotkeys.md", Hotkeys.generateDocumentation(), []) +{ + new ThemeViewState(new LayoutConfig(bookcases)) + WriteFile("./Docs/Hotkeys.md", Hotkeys.generateDocumentation(), []) +} console.log("Generated docs") From 1123a72c5e6586cb6c72366bd3e66231c9f6789f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 16 Apr 2023 03:42:26 +0200 Subject: [PATCH 041/257] Refactoring: fix most of the custom input elements, support right click/long tap/double click to add a new element --- .../PerLayerFeatureSourceSplitter.ts | 1 - UI/Input/ColorPicker.ts | 39 ---- UI/Input/CombinedInputElement.ts | 47 ----- UI/Input/FileSelectorButton.ts | 1 - UI/Input/LengthInput.ts | 187 ------------------ UI/Input/SimpleDatePicker.ts | 37 ---- UI/InputElement/Helpers/ColorInput.svelte | 12 ++ UI/InputElement/Helpers/DateInput.svelte | 12 ++ UI/InputElement/Helpers/DirectionInput.svelte | 9 +- UI/InputElement/InputHelper.svelte | 23 ++- UI/InputElement/InputHelpers.ts | 155 ++++++++++++++- UI/InputElement/ValidatedInput.svelte | 9 +- .../Validators/DirectionValidator.ts | 7 +- .../Validators/WikidataValidator.ts | 86 +------- UI/Map/MapLibreAdaptor.ts | 27 ++- UI/OpeningHours/OpeningHoursInput.ts | 3 +- UI/Popup/DeleteWizard.ts | 8 +- UI/Popup/TagHint.svelte | 2 +- UI/Popup/TagRendering/FreeformInput.svelte | 26 ++- UI/Popup/TagRendering/Questionbox.svelte | 7 +- .../TagRendering/TagRenderingAnswer.svelte | 32 +-- .../TagRendering/TagRenderingQuestion.svelte | 16 +- UI/Popup/TagRenderingQuestion.ts | 62 +----- UI/Wikipedia/WikidataSearchBox.ts | 83 +++++++- test.ts | 30 ++- 25 files changed, 390 insertions(+), 531 deletions(-) delete mode 100644 UI/Input/ColorPicker.ts delete mode 100644 UI/Input/CombinedInputElement.ts delete mode 100644 UI/Input/LengthInput.ts delete mode 100644 UI/Input/SimpleDatePicker.ts create mode 100644 UI/InputElement/Helpers/ColorInput.svelte create mode 100644 UI/InputElement/Helpers/DateInput.svelte diff --git a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts index 2449814963..f437a90d69 100644 --- a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts +++ b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts @@ -25,7 +25,6 @@ export default class PerLayerFeatureSourceSplitter< const knownLayers = new Map() this.perLayer = knownLayers const layerSources = new Map>() - console.log("PerLayerFeatureSourceSplitter got layers", layers) const constructStore = options?.constructStore ?? ((store, layer) => new SimpleFeatureSource(layer, store)) for (const layer of layers) { diff --git a/UI/Input/ColorPicker.ts b/UI/Input/ColorPicker.ts deleted file mode 100644 index 3960c6ccbb..0000000000 --- a/UI/Input/ColorPicker.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { InputElement } from "./InputElement" -import { UIEventSource } from "../../Logic/UIEventSource" - -export default class ColorPicker extends InputElement { - IsSelected: UIEventSource = new UIEventSource(false) - private readonly value: UIEventSource - private readonly _element: HTMLElement - - constructor(value: UIEventSource = new UIEventSource(undefined)) { - super() - this.value = value - - const el = document.createElement("input") - this._element = el - - el.type = "color" - - this.value.addCallbackAndRunD((v) => { - el.value = v - }) - - el.oninput = () => { - const hex = el.value - value.setData(hex) - } - } - - GetValue(): UIEventSource { - return this.value - } - - IsValid(t: string): boolean { - return false - } - - protected InnerConstructElement(): HTMLElement { - return this._element - } -} diff --git a/UI/Input/CombinedInputElement.ts b/UI/Input/CombinedInputElement.ts deleted file mode 100644 index 2af732833e..0000000000 --- a/UI/Input/CombinedInputElement.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { InputElement } from "./InputElement" -import { UIEventSource } from "../../Logic/UIEventSource" -import Combine from "../Base/Combine" -import BaseUIElement from "../BaseUIElement" - -export default class CombinedInputElement extends InputElement { - private readonly _a: InputElement - private readonly _b: InputElement - private readonly _combined: BaseUIElement - private readonly _value: UIEventSource - private readonly _split: (x: X) => [T, J] - - constructor( - a: InputElement, - b: InputElement, - combine: (t: T, j: J) => X, - split: (x: X) => [T, J] - ) { - super() - this._a = a - this._b = b - this._split = split - this._combined = new Combine([this._a, this._b]) - this._value = this._a.GetValue().sync( - (t) => combine(t, this._b?.GetValue()?.data), - [this._b.GetValue()], - (x) => { - const [t, j] = split(x) - this._b.GetValue()?.setData(j) - return t - } - ) - } - - GetValue(): UIEventSource { - return this._value - } - - IsValid(x: X): boolean { - const [t, j] = this._split(x) - return this._a.IsValid(t) && this._b.IsValid(j) - } - - protected InnerConstructElement(): HTMLElement { - return this._combined.ConstructElement() - } -} diff --git a/UI/Input/FileSelectorButton.ts b/UI/Input/FileSelectorButton.ts index ae0b0ba429..5248cf51c6 100644 --- a/UI/Input/FileSelectorButton.ts +++ b/UI/Input/FileSelectorButton.ts @@ -4,7 +4,6 @@ import { UIEventSource } from "../../Logic/UIEventSource" export default class FileSelectorButton extends InputElement { private static _nextid - IsSelected: UIEventSource private readonly _value = new UIEventSource(undefined) private readonly _label: BaseUIElement private readonly _acceptType: string diff --git a/UI/Input/LengthInput.ts b/UI/Input/LengthInput.ts deleted file mode 100644 index 4a28e5ea1b..0000000000 --- a/UI/Input/LengthInput.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { InputElement } from "./InputElement"; -import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"; -import Combine from "../Base/Combine"; -import Svg from "../../Svg"; -import Loc from "../../Models/Loc"; -import { GeoOperations } from "../../Logic/GeoOperations"; -import BaseUIElement from "../BaseUIElement"; -import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers"; - -/** - * Selects a length after clicking on the minimap, in meters - */ -export default class LengthInput extends InputElement { - private readonly _location: Store - private readonly value: UIEventSource - private readonly background: Store - - constructor( - location: UIEventSource, - mapBackground?: UIEventSource, - value?: UIEventSource - ) { - super() - this._location = location - this.value = value ?? new UIEventSource(undefined) - this.background = mapBackground ?? new ImmutableStore(AvailableRasterLayers.osmCarto) - this.SetClass("block") - } - - GetValue(): UIEventSource { - return this.value - } - - IsValid(str: string): boolean { - const t = Number(str) - return !isNaN(t) && t >= 0 - } - - protected InnerConstructElement(): HTMLElement { - let map: BaseUIElement = undefined - let layerControl: BaseUIElement = undefined - map = Minimap.createMiniMap({ - background: this.background, - allowMoving: false, - location: this._location, - attribution: true, - leafletOptions: { - tap: true, - }, - }) - - const crosshair = new Combine([ - Svg.length_crosshair_svg().SetStyle( - `position: absolute;top: 0;left: 0;transform:rotate(${this.value.data ?? 0}deg);` - ), - ]) - .SetClass("block length-crosshair-svg relative pointer-events-none") - .SetStyle("z-index: 1000; visibility: hidden") - - const element = new Combine([ - crosshair, - map?.SetClass("w-full h-full block absolute top-0 left-O overflow-hidden"), - ]) - .SetClass("relative block bg-white border border-black rounded-xl overflow-hidden") - .ConstructElement() - - this.RegisterTriggers( - map?.ConstructElement(), - map?.leafletMap, - crosshair.ConstructElement() - ) - element.style.overflow = "hidden" - element.style.display = "block" - - return element - } - - private RegisterTriggers( - htmlElement: HTMLElement, - leafletMap: UIEventSource, - measurementCrosshair: HTMLElement - ) { - let firstClickXY: [number, number] = undefined - let lastClickXY: [number, number] = undefined - const self = this - - function onPosChange(x: number, y: number, isDown: boolean, isUp?: boolean) { - if (x === undefined || y === undefined) { - // Touch end - firstClickXY = undefined - lastClickXY = undefined - return - } - - const rect = htmlElement.getBoundingClientRect() - // From the central part of location - const dx = x - rect.left - const dy = y - rect.top - if (isDown) { - if (lastClickXY === undefined && firstClickXY === undefined) { - firstClickXY = [dx, dy] - } else if (firstClickXY !== undefined && lastClickXY === undefined) { - lastClickXY = [dx, dy] - } else if (firstClickXY !== undefined && lastClickXY !== undefined) { - // we measure again - firstClickXY = [dx, dy] - lastClickXY = undefined - } - } - - if (firstClickXY === undefined) { - measurementCrosshair.style.visibility = "hidden" - return - } - - const distance = Math.sqrt( - (dy - firstClickXY[1]) * (dy - firstClickXY[1]) + - (dx - firstClickXY[0]) * (dx - firstClickXY[0]) - ) - if (isUp) { - if (distance > 15) { - lastClickXY = [dx, dy] - } - } else if (lastClickXY !== undefined) { - return - } - measurementCrosshair.style.visibility = "unset" - measurementCrosshair.style.left = firstClickXY[0] + "px" - measurementCrosshair.style.top = firstClickXY[1] + "px" - - const angle = (180 * Math.atan2(firstClickXY[1] - dy, firstClickXY[0] - dx)) / Math.PI - const angleGeo = (angle + 270) % 360 - const measurementCrosshairInner: HTMLElement = ( - measurementCrosshair.firstChild - ) - measurementCrosshairInner.style.transform = `rotate(${angleGeo}deg)` - - measurementCrosshairInner.style.width = distance * 2 + "px" - measurementCrosshairInner.style.marginLeft = -distance + "px" - measurementCrosshairInner.style.marginTop = -distance + "px" - - const leaflet = leafletMap?.data - if (leaflet) { - const first = leaflet.layerPointToLatLng(firstClickXY) - const last = leaflet.layerPointToLatLng([dx, dy]) - const geoDist = - Math.floor( - GeoOperations.distanceBetween( - [first.lng, first.lat], - [last.lng, last.lat] - ) * 10 - ) / 10 - self.value.setData("" + geoDist) - } - } - - htmlElement.ontouchstart = (ev: TouchEvent) => { - onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, true) - ev.preventDefault() - } - - htmlElement.ontouchmove = (ev: TouchEvent) => { - onPosChange(ev.touches[0].clientX, ev.touches[0].clientY, false) - ev.preventDefault() - } - - htmlElement.ontouchend = (ev: TouchEvent) => { - onPosChange(undefined, undefined, false, true) - ev.preventDefault() - } - - htmlElement.onmousedown = (ev: MouseEvent) => { - onPosChange(ev.clientX, ev.clientY, true) - ev.preventDefault() - } - - htmlElement.onmouseup = (ev) => { - onPosChange(ev.clientX, ev.clientY, false, true) - ev.preventDefault() - } - - htmlElement.onmousemove = (ev: MouseEvent) => { - onPosChange(ev.clientX, ev.clientY, false) - ev.preventDefault() - } - } -} diff --git a/UI/Input/SimpleDatePicker.ts b/UI/Input/SimpleDatePicker.ts deleted file mode 100644 index 8600830821..0000000000 --- a/UI/Input/SimpleDatePicker.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { InputElement } from "./InputElement" -import { UIEventSource } from "../../Logic/UIEventSource" - -export default class SimpleDatePicker extends InputElement { - private readonly value: UIEventSource - private readonly _element: HTMLElement - - constructor(value?: UIEventSource) { - super() - this.value = value ?? new UIEventSource(undefined) - const self = this - - const el = document.createElement("input") - this._element = el - el.type = "date" - el.oninput = () => { - // Already in YYYY-MM-DD value! - self.value.setData(el.value) - } - - this.value.addCallbackAndRunD((v) => { - el.value = v - }) - } - - GetValue(): UIEventSource { - return this.value - } - - IsValid(t: string): boolean { - return !isNaN(new Date(t).getTime()) - } - - protected InnerConstructElement(): HTMLElement { - return this._element - } -} diff --git a/UI/InputElement/Helpers/ColorInput.svelte b/UI/InputElement/Helpers/ColorInput.svelte new file mode 100644 index 0000000000..ee736d703e --- /dev/null +++ b/UI/InputElement/Helpers/ColorInput.svelte @@ -0,0 +1,12 @@ + + + + diff --git a/UI/InputElement/Helpers/DateInput.svelte b/UI/InputElement/Helpers/DateInput.svelte new file mode 100644 index 0000000000..2f8c2d21c3 --- /dev/null +++ b/UI/InputElement/Helpers/DateInput.svelte @@ -0,0 +1,12 @@ + + + + diff --git a/UI/InputElement/Helpers/DirectionInput.svelte b/UI/InputElement/Helpers/DirectionInput.svelte index cf63be378c..ea1c6ec3dd 100644 --- a/UI/InputElement/Helpers/DirectionInput.svelte +++ b/UI/InputElement/Helpers/DirectionInput.svelte @@ -8,9 +8,9 @@ import Svg from "../../../Svg.js"; /** - * A visualisation to pick a direction on a map background + * A visualisation to pick a direction on a map background. */ - export let value: UIEventSource; + export let value: UIEventSource; export let mapProperties: Partial & { readonly location: UIEventSource<{ lon: number; lat: number }> }; let map: UIEventSource = new UIEventSource(undefined); let mla = new MapLibreAdaptor(map, mapProperties); @@ -18,7 +18,6 @@ mla.allowZooming.setData(false) let directionElem: HTMLElement | undefined; $: value.addCallbackAndRunD(degrees => { - console.log("Degrees are", degrees, directionElem); if (directionElem === undefined) { return; } @@ -32,7 +31,7 @@ const dy = (rect.top + rect.bottom) / 2 - y; const angle = (180 * Math.atan2(dy, dx)) / Math.PI; const angleGeo = Math.floor((450 - angle) % 360); - value.setData(angleGeo); + value.setData(""+angleGeo); } let isDown = false; @@ -61,7 +60,7 @@
-
+
diff --git a/UI/InputElement/InputHelper.svelte b/UI/InputElement/InputHelper.svelte index e64305a67c..7d480abfca 100644 --- a/UI/InputElement/InputHelper.svelte +++ b/UI/InputElement/InputHelper.svelte @@ -3,11 +3,24 @@ * Constructs an input helper element for the given type. * Note that all values are stringified */ - - import { AvailableInputHelperType } from "./InputHelpers"; - import { UIEventSource } from "../../Logic/UIEventSource"; - export let type : AvailableInputHelperType - export let value : UIEventSource + import { UIEventSource } from "../../Logic/UIEventSource"; + import type { ValidatorType } from "./Validators"; + import InputHelpers from "./InputHelpers"; + import ToSvelte from "../Base/ToSvelte.svelte"; + import type { Feature } from "geojson"; + + export let type: ValidatorType; + export let value: UIEventSource; + export let feature: Feature; + export let args: (string | number | boolean)[] = undefined; + + let properties = { feature, args: args ?? [] }; + let construct = InputHelpers.AvailableInputHelpers[type]; + + +{#if construct !== undefined} + construct(value, properties)} /> +{/if} diff --git a/UI/InputElement/InputHelpers.ts b/UI/InputElement/InputHelpers.ts index ff6c14f8bb..3090713904 100644 --- a/UI/InputElement/InputHelpers.ts +++ b/UI/InputElement/InputHelpers.ts @@ -1,16 +1,151 @@ -import { AvailableRasterLayers } from "../../Models/RasterLayers" +import { ValidatorType } from "./Validators" +import { UIEventSource } from "../../Logic/UIEventSource" +import SvelteUIElement from "../Base/SvelteUIElement" +import DirectionInput from "./Helpers/DirectionInput.svelte" +import { MapProperties } from "../../Models/MapProperties" +import DateInput from "./Helpers/DateInput.svelte" +import ColorInput from "./Helpers/ColorInput.svelte" +import BaseUIElement from "../BaseUIElement" +import OpeningHoursInput from "../OpeningHours/OpeningHoursInput" +import WikidataSearchBox from "../Wikipedia/WikidataSearchBox" +import Wikidata from "../../Logic/Web/Wikidata" +import { Utils } from "../../Utils" +import Locale from "../i18n/Locale" +import { Feature } from "geojson" +import { GeoOperations } from "../../Logic/GeoOperations" -export type AvailableInputHelperType = typeof InputHelpers.AvailableInputHelpers[number] +export interface InputHelperProperties { + /** + * Extra arguments which might be used by the helper component + */ + args?: (string | number | boolean)[] + + /** + * Used for map-based helpers, such as 'direction' + */ + mapProperties?: Partial & { + readonly location: UIEventSource<{ lon: number; lat: number }> + } + /** + * The feature that this question is about + * Used by the wikidata-input to read properties, which in turn is used to read the name to pre-populate the text field. + * Additionally, used for direction input to set the default location if no mapProperties with location are given + */ + feature?: Feature +} export default class InputHelpers { - public static readonly AvailableInputHelpers = [] as const + public static readonly AvailableInputHelpers: Readonly< + Partial< + Record< + ValidatorType, + ( + value: UIEventSource, + extraProperties?: InputHelperProperties + ) => BaseUIElement + > + > + > = { + direction: (value, properties) => + new SvelteUIElement(DirectionInput, { + value, + mapProperties: InputHelpers.constructMapProperties(properties), + }), + date: (value) => new SvelteUIElement(DateInput, { value }), + color: (value) => new SvelteUIElement(ColorInput, { value }), + opening_hours: (value) => new OpeningHoursInput(value), + wikidata: InputHelpers.constructWikidataHelper, + } as const + /** - * To port - * direction - * opening_hours - * color - * length - * date - * wikidata + * Constructs a mapProperties-object for the given properties. + * Assumes that the first helper-args contains the desired zoom-level + * @param properties + * @private */ + private static constructMapProperties( + properties: InputHelperProperties + ): Partial { + let location = properties?.mapProperties?.location + if (!location) { + const [lon, lat] = GeoOperations.centerpointCoordinates(properties.feature) + location = new UIEventSource<{ lon: number; lat: number }>({ lon, lat }) + } + let mapProperties: Partial = properties?.mapProperties ?? { location } + if (!mapProperties.location) { + mapProperties = { ...mapProperties, location } + } + let zoom = 17 + if (properties.args[0]) { + zoom = Number(properties.args[0]) + if (isNaN(zoom)) { + throw "Invalid zoom level for argument at 'length'-input" + } + } + if (!mapProperties.zoom) { + mapProperties = { ...mapProperties, zoom: new UIEventSource(zoom) } + } + return mapProperties + } + private static constructWikidataHelper( + value: UIEventSource, + props: InputHelperProperties + ) { + const inputHelperOptions = props + const args = inputHelperOptions.args ?? [] + const searchKey = args[0] ?? "name" + + const searchFor = ( + (inputHelperOptions.feature?.properties[searchKey]?.toLowerCase() ?? "") + ) + + let searchForValue: UIEventSource = new UIEventSource(searchFor) + const options: any = args[1] + if (searchFor !== undefined && options !== undefined) { + const prefixes = >options["removePrefixes"] ?? [] + const postfixes = >options["removePostfixes"] ?? [] + const defaultValueCandidate = Locale.language.map((lg) => { + const prefixesUnrwapped: RegExp[] = ( + Array.isArray(prefixes) ? prefixes : prefixes[lg] ?? [] + ).map((s) => new RegExp("^" + s, "i")) + const postfixesUnwrapped: RegExp[] = ( + Array.isArray(postfixes) ? postfixes : postfixes[lg] ?? [] + ).map((s) => new RegExp(s + "$", "i")) + let clipped = searchFor + + for (const postfix of postfixesUnwrapped) { + const match = searchFor.match(postfix) + if (match !== null) { + clipped = searchFor.substring(0, searchFor.length - match[0].length) + break + } + } + + for (const prefix of prefixesUnrwapped) { + const match = searchFor.match(prefix) + if (match !== null) { + clipped = searchFor.substring(match[0].length) + break + } + } + return clipped + }) + + defaultValueCandidate.addCallbackAndRun((clipped) => searchForValue.setData(clipped)) + } + + let instanceOf: number[] = Utils.NoNull( + (options?.instanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)) + ) + let notInstanceOf: number[] = Utils.NoNull( + (options?.notInstanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)) + ) + + return new WikidataSearchBox({ + value, + searchText: searchForValue, + instanceOf, + notInstanceOf, + }) + } } diff --git a/UI/InputElement/ValidatedInput.svelte b/UI/InputElement/ValidatedInput.svelte index 91b55755ce..761ef01e2d 100644 --- a/UI/InputElement/ValidatedInput.svelte +++ b/UI/InputElement/ValidatedInput.svelte @@ -5,15 +5,16 @@ import Validators from "./Validators"; import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"; import { Translation } from "../i18n/Translation"; - import { createEventDispatcher } from "svelte"; + import { createEventDispatcher, onDestroy } from "svelte"; export let value: UIEventSource; // Internal state, only copied to 'value' so that no invalid values leak outside let _value = new UIEventSource(value.data ?? ""); + onDestroy(value.addCallbackAndRun(v => _value.setData(v ?? ""))); export let type: ValidatorType; let validator = Validators.get(type); export let feedback: UIEventSource | undefined = undefined; - _value.addCallbackAndRun(v => { + onDestroy(_value.addCallbackAndRun(v => { if (validator.isValid(v)) { feedback?.setData(undefined); value.setData(v); @@ -21,7 +22,7 @@ } value.setData(undefined); feedback?.setData(validator.getFeedback(v)); - }); + })) if (validator === undefined) { throw "Not a valid type for a validator:" + type; @@ -46,7 +47,7 @@ {#if validator.textArea} {:else } - + {#if !$isValid} diff --git a/UI/InputElement/Validators/DirectionValidator.ts b/UI/InputElement/Validators/DirectionValidator.ts index 5f96184544..4cf14d9ad4 100644 --- a/UI/InputElement/Validators/DirectionValidator.ts +++ b/UI/InputElement/Validators/DirectionValidator.ts @@ -1,11 +1,14 @@ import IntValidator from "./IntValidator" -import { Validator } from "../Validator" export default class DirectionValidator extends IntValidator { constructor() { super( "direction", - "A geographical direction, in degrees. 0° is north, 90° is east, ... Will return a value between 0 (incl) and 360 (excl)" + [ + "A geographical direction, in degrees. 0° is north, 90° is east, ... Will return a value between 0 (incl) and 360 (excl).", + "### Input helper", + "This element has an input helper showing a map and 'viewport' indicating the direction. By default, this map is zoomed to zoomlevel 17, but this can be changed with the first argument", + ].join("\n\n") ) } diff --git a/UI/InputElement/Validators/WikidataValidator.ts b/UI/InputElement/Validators/WikidataValidator.ts index 6a6cf12dfe..cb031f4477 100644 --- a/UI/InputElement/Validators/WikidataValidator.ts +++ b/UI/InputElement/Validators/WikidataValidator.ts @@ -1,6 +1,4 @@ import Combine from "../../Base/Combine" -import Title from "../../Base/Title" -import Table from "../../Base/Table" import Wikidata from "../../../Logic/Web/Wikidata" import { UIEventSource } from "../../../Logic/UIEventSource" import Locale from "../../i18n/Locale" @@ -10,89 +8,7 @@ import { Validator } from "../Validator" export default class WikidataValidator extends Validator { constructor() { - super( - "wikidata", - new Combine([ - "A wikidata identifier, e.g. Q42.", - new Title("Helper arguments"), - new Table( - ["name", "doc"], - [ - ["key", "the value of this tag will initialize search (default: name)"], - [ - "options", - new Combine([ - "A JSON-object of type `{ removePrefixes: string[], removePostfixes: string[] }`.", - new Table( - ["subarg", "doc"], - [ - [ - "removePrefixes", - "remove these snippets of text from the start of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes", - ], - [ - "removePostfixes", - "remove these snippets of text from the end of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes.", - ], - [ - "instanceOf", - "A list of Q-identifier which indicates that the search results _must_ be an entity of this type, e.g. [`Q5`](https://www.wikidata.org/wiki/Q5) for humans", - ], - [ - "notInstanceof", - "A list of Q-identifiers which indicates that the search results _must not_ be an entity of this type, e.g. [`Q79007`](https://www.wikidata.org/wiki/Q79007) to filter away all streets from the search results", - ], - ] - ), - ]), - ], - ] - ), - new Title("Example usage"), - `The following is the 'freeform'-part of a layer config which will trigger a search for the wikidata item corresponding with the name of the selected feature. It will also remove '-street', '-square', ... if found at the end of the name - -\`\`\`json -"freeform": { - "key": "name:etymology:wikidata", - "type": "wikidata", - "helperArgs": [ - "name", - { - "removePostfixes": {"en": [ - "street", - "boulevard", - "path", - "square", - "plaza", - ], - "nl": ["straat","plein","pad","weg",laan"], - "fr":["route (de|de la|de l'| de le)"] - }, - - "#": "Remove streets and parks from the search results:" - "notInstanceOf": ["Q79007","Q22698"] - } - - ] -} -\`\`\` - -Another example is to search for species and trees: - -\`\`\`json - "freeform": { - "key": "species:wikidata", - "type": "wikidata", - "helperArgs": [ - "species", - { - "instanceOf": [10884, 16521] - }] - } -\`\`\` -`, - ]) - ) + super("wikidata", new Combine(["A wikidata identifier, e.g. Q42.", WikidataSearchBox.docs])) } public isValid(str): boolean { diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index b05d3c0bef..74a9aa0f95 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -67,6 +67,18 @@ export class MapLibreAdaptor implements MapProperties { const lastClickLocation = new UIEventSource<{ lon: number; lat: number }>(undefined) this.lastClickLocation = lastClickLocation const self = this + + function handleClick(e) { + if (e.originalEvent["consumed"]) { + // Workaround, 'ShowPointLayer' sets this flag + return + } + console.log(e) + const lon = e.lngLat.lng + const lat = e.lngLat.lat + lastClickLocation.setData({ lon, lat }) + } + maplibreMap.addCallbackAndRunD((map) => { map.on("load", () => { this.updateStores() @@ -87,14 +99,13 @@ export class MapLibreAdaptor implements MapProperties { this.updateStores() map.on("moveend", () => this.updateStores()) map.on("click", (e) => { - if (e.originalEvent["consumed"]) { - // Workaround, 'ShowPointLayer' sets this flag - return - } - console.log(e) - const lon = e.lngLat.lng - const lat = e.lngLat.lat - lastClickLocation.setData({ lon, lat }) + handleClick(e) + }) + map.on("contextmenu", (e) => { + handleClick(e) + }) + map.on("dblclick", (e) => { + handleClick(e) }) }) diff --git a/UI/OpeningHours/OpeningHoursInput.ts b/UI/OpeningHours/OpeningHoursInput.ts index d301a19cdc..94b0089daa 100644 --- a/UI/OpeningHours/OpeningHoursInput.ts +++ b/UI/OpeningHours/OpeningHoursInput.ts @@ -1,7 +1,7 @@ /** * The full opening hours element, including the table, opening hours picker. * Keeps track of unparsed rules - * Exports everything conventiently as a string, for direct use + * Exports everything conveniently as a string, for direct use */ import OpeningHoursPicker from "./OpeningHoursPicker" import { Store, UIEventSource } from "../../Logic/UIEventSource" @@ -15,7 +15,6 @@ import Translations from "../i18n/Translations" import BaseUIElement from "../BaseUIElement" export default class OpeningHoursInput extends InputElement { - public readonly IsSelected: UIEventSource = new UIEventSource(false) private readonly _value: UIEventSource private readonly _element: BaseUIElement diff --git a/UI/Popup/DeleteWizard.ts b/UI/Popup/DeleteWizard.ts index 84f0d552ed..2cd325c6ff 100644 --- a/UI/Popup/DeleteWizard.ts +++ b/UI/Popup/DeleteWizard.ts @@ -24,6 +24,8 @@ import TagRenderingQuestion from "./TagRenderingQuestion" import { OsmId, OsmTags } from "../../Models/OsmFeature" import { LoginToggle } from "./LoginButton" import { SpecialVisualizationState } from "../SpecialVisualization" +import SvelteUIElement from "../Base/SvelteUIElement"; +import TagHint from "./TagHint.svelte"; export default class DeleteWizard extends Toggle { /** @@ -225,11 +227,7 @@ export default class DeleteWizard extends Toggle { // This is a retagging, not a deletion of any kind return new Combine([ t.explanations.retagNoOtherThemes, - TagRenderingQuestion.CreateTagExplanation( - new UIEventSource(retag), - currentTags, - state - ).SetClass("subtle"), + new SvelteUIElement(TagHint, {osmConnection: state.osmConnection, tags: retag}) ]) } diff --git a/UI/Popup/TagHint.svelte b/UI/Popup/TagHint.svelte index 377d0736e3..8cd6a286ee 100644 --- a/UI/Popup/TagHint.svelte +++ b/UI/Popup/TagHint.svelte @@ -11,13 +11,13 @@ * A 'TagHint' will show the given tags in a human readable form. * Depending on the options, it'll link through to the wiki or might be completely hidden */ + export let tags: TagsFilter; export let osmConnection: OsmConnection; /** * If given, this function will be called to embed the given tags hint into this translation */ export let embedIn: (() => Translation) | undefined = undefined; const userDetails = osmConnection.userDetails; - export let tags: TagsFilter; let linkToWiki = false; onDestroy(osmConnection.userDetails.addCallbackAndRunD(userdetails => { linkToWiki = userdetails.csCount > Constants.userJourney.tagsVisibleAndWikiLinked; diff --git a/UI/Popup/TagRendering/FreeformInput.svelte b/UI/Popup/TagRendering/FreeformInput.svelte index 77362785ac..88df41b521 100644 --- a/UI/Popup/TagRendering/FreeformInput.svelte +++ b/UI/Popup/TagRendering/FreeformInput.svelte @@ -5,28 +5,36 @@ import Tr from "../../Base/Tr.svelte"; import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"; import Inline from "./Inline.svelte"; - import { createEventDispatcher } from "svelte"; + import { createEventDispatcher, onDestroy } from "svelte"; + import InputHelper from "../../InputElement/InputHelper.svelte"; + import type { Feature } from "geojson"; export let value: UIEventSource; export let config: TagRenderingConfig; export let tags: UIEventSource>; + export let feature: Feature = undefined; + let feedback: UIEventSource = new UIEventSource(undefined); let dispatch = createEventDispatcher<{ "selected" }>(); + onDestroy(value.addCallbackD(() => {dispatch("selected")})) -{#if config.freeform.inline} - +
+ + {#if config.freeform.inline} + + dispatch("selected")} + type={config.freeform.type} {value}> + + {:else} dispatch("selected")} type={config.freeform.type} {value}> - -{:else} - dispatch("selected")} - type={config.freeform.type} {value}> - -{/if} + {/if} + +
{#if $feedback !== undefined}
diff --git a/UI/Popup/TagRendering/Questionbox.svelte b/UI/Popup/TagRendering/Questionbox.svelte index 8bfc71a019..2daecabd72 100644 --- a/UI/Popup/TagRendering/Questionbox.svelte +++ b/UI/Popup/TagRendering/Questionbox.svelte @@ -41,13 +41,11 @@ return true; } - let baseQuestions = [] - $: { - baseQuestions = (layer.tagRenderings ?? [])?.filter(tr => allowed(tr.labels) && tr.question !== undefined); - } let skippedQuestions = new UIEventSource>(new Set()); let questionsToAsk = tags.map(tags => { + const baseQuestions = (layer.tagRenderings ?? [])?.filter(tr => allowed(tr.labels) && tr.question !== undefined); + console.log("Determining questions for", baseQuestions) const questionsToAsk: TagRenderingConfig[] = []; for (const baseQuestion of baseQuestions) { if (skippedQuestions.data.has(baseQuestion.id) > 0) { @@ -64,6 +62,7 @@ return questionsToAsk; }, [skippedQuestions]); + let _questionsToAsk: TagRenderingConfig[]; let _firstQuestion: TagRenderingConfig; onDestroy(questionsToAsk.subscribe(qta => { diff --git a/UI/Popup/TagRendering/TagRenderingAnswer.svelte b/UI/Popup/TagRendering/TagRenderingAnswer.svelte index bcc58f715f..1a583e282e 100644 --- a/UI/Popup/TagRendering/TagRenderingAnswer.svelte +++ b/UI/Popup/TagRendering/TagRenderingAnswer.svelte @@ -17,25 +17,27 @@ export let state: SpecialVisualizationState; export let selectedElement: Feature; export let config: TagRenderingConfig; - if(config === undefined){ - throw "Config is undefined in tagRenderingAnswer" + if (config === undefined) { + throw "Config is undefined in tagRenderingAnswer"; } - export let layer: LayerConfig + export let layer: LayerConfig; let trs: { then: Translation; icon?: string; iconClass?: string }[]; $: trs = Utils.NoNull(config?.GetRenderValues(_tags)); {#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties(_tags))} - {#if trs.length === 1} - - {/if} - {#if trs.length > 1} -
    - {#each trs as mapping} -
  • - -
  • - {/each} -
- {/if} +
+ {#if trs.length === 1} + + {/if} + {#if trs.length > 1} +
    + {#each trs as mapping} +
  • + +
  • + {/each} +
+ {/if} +
{/if} diff --git a/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/UI/Popup/TagRendering/TagRenderingQuestion.svelte index 38e681ae50..d26468d669 100644 --- a/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -11,7 +11,7 @@ import FreeformInput from "./FreeformInput.svelte"; import Translations from "../../i18n/Translations.js"; import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"; - import { createEventDispatcher } from "svelte"; + import { createEventDispatcher, onDestroy } from "svelte"; import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"; import SpecialTranslation from "./SpecialTranslation.svelte"; @@ -25,6 +25,12 @@ // Will be bound if a freeform is available let freeformInput = new UIEventSource(undefined); + onDestroy(tags.addCallbackAndRunD(tags => { + // initialize with the previous value + if (config.freeform?.key) { + freeformInput.setData(tags[config.freeform.key]); + } + })); let selectedMapping: number = undefined; let checkedMappings: boolean[]; $: { @@ -126,7 +132,7 @@ {#if config.freeform?.key && !(mappings?.length > 0)} - + {:else if mappings !== undefined && !config.multiAnswer}
@@ -143,7 +149,7 @@ {/if} @@ -162,7 +168,7 @@ {/if} @@ -180,7 +186,7 @@ {:else }
- +
{/if}
diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index 72c1f80dad..f2ea087f3f 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -25,10 +25,8 @@ import TagRenderingConfig, { Mapping } from "../../Models/ThemeConfig/TagRenderi import { Unit } from "../../Models/Unit" import VariableInputElement from "../Input/VariableInputElement" import Toggle from "../Input/Toggle" -import Img from "../Base/Img" import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import Title from "../Base/Title" -import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { GeoOperations } from "../../Logic/GeoOperations" import { SearchablePillsSelector } from "../Input/SearchableMappingsSelector" import { OsmTags } from "../../Models/OsmFeature" @@ -47,7 +45,6 @@ export default class TagRenderingQuestion extends Combine { afterSave?: () => void cancelButton?: BaseUIElement saveButtonConstr?: (src: Store) => BaseUIElement - bottomText?: (src: Store) => BaseUIElement } ) { const applicableMappingsSrc = Stores.ListStabilized( @@ -134,26 +131,15 @@ export default class TagRenderingQuestion extends Combine { const saveButton = new Combine([options.saveButtonConstr(inputElement.GetValue())]) - let bottomTags: BaseUIElement - if (options.bottomText !== undefined) { - bottomTags = options.bottomText(inputElement.GetValue()) - } else { - bottomTags = TagRenderingQuestion.CreateTagExplanation( - inputElement.GetValue(), - tags, - state - ) - } super([ question, questionHint, inputElement, new VariableUiElement( - feedback.map( - (t) => - t - ?.SetStyle("padding-left: 0.75rem; padding-right: 0.75rem") - ?.SetClass("alert flex") ?? bottomTags + feedback.map((t) => + t + ?.SetStyle("padding-left: 0.75rem; padding-right: 0.75rem") + ?.SetClass("alert flex") ) ), new Combine([options.cancelButton, saveButton]).SetClass( @@ -634,14 +620,7 @@ export default class TagRenderingQuestion extends Combine { tagsSource: UIEventSource, state: FeaturePipelineState ): BaseUIElement { - const text = new SubstitutedTranslation(mapping.then, tagsSource, state) - if (mapping.icon === undefined) { - return text - } - return new Combine([ - new Img(mapping.icon).SetClass("mr-1 mapping-icon-" + (mapping.iconClass ?? "small")), - text, - ]).SetClass("flex items-center") + return undefined } private static GenerateFreeform( @@ -703,9 +682,6 @@ export default class TagRenderingQuestion extends Combine { feedback, }) - // Init with correct value - input?.GetValue().setData(tagsData[freeform.key] ?? freeform.default) - // Add a length check input?.GetValue().addCallbackD((v: string | undefined) => { if (v?.length >= 255) { @@ -734,32 +710,4 @@ export default class TagRenderingQuestion extends Combine { return inputTagsFilter } - - public static CreateTagExplanation( - selectedValue: Store, - tags: Store, - state?: { osmConnection?: OsmConnection } - ) { - return new VariableUiElement( - selectedValue.map( - (tagsFilter: TagsFilter) => { - const csCount = - state?.osmConnection?.userDetails?.data?.csCount ?? - Constants.userJourney.tagsVisibleAndWikiLinked + 1 - if (csCount < Constants.userJourney.tagsVisibleAt) { - return "" - } - if (tagsFilter === undefined) { - return Translations.t.general.noTagsSelected.SetClass("subtle") - } - if (csCount < Constants.userJourney.tagsVisibleAndWikiLinked) { - const tagsStr = tagsFilter.asHumanString(false, true, tags.data) - return new FixedUiElement(tagsStr).SetClass("subtle") - } - return tagsFilter.asHumanString(true, true, tags.data) - }, - [state?.osmConnection?.userDetails] - ) - ).SetClass("block break-all") - } } diff --git a/UI/Wikipedia/WikidataSearchBox.ts b/UI/Wikipedia/WikidataSearchBox.ts index 5c7f2ba3b2..d411031aaa 100644 --- a/UI/Wikipedia/WikidataSearchBox.ts +++ b/UI/Wikipedia/WikidataSearchBox.ts @@ -11,15 +11,96 @@ import Title from "../Base/Title" import WikipediaBox from "./WikipediaBox" import Svg from "../../Svg" import Loading from "../Base/Loading" +import Table from "../Base/Table" export default class WikidataSearchBox extends InputElement { private static readonly _searchCache = new Map>() - IsSelected: UIEventSource = new UIEventSource(false) private readonly wikidataId: UIEventSource private readonly searchText: UIEventSource private readonly instanceOf?: number[] private readonly notInstanceOf?: number[] + public static docs = new Combine([ + , + new Title("Helper arguments"), + new Table( + ["name", "doc"], + [ + ["key", "the value of this tag will initialize search (default: name)"], + [ + "options", + new Combine([ + "A JSON-object of type `{ removePrefixes: string[], removePostfixes: string[] }`.", + new Table( + ["subarg", "doc"], + [ + [ + "removePrefixes", + "remove these snippets of text from the start of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes", + ], + [ + "removePostfixes", + "remove these snippets of text from the end of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes.", + ], + [ + "instanceOf", + "A list of Q-identifier which indicates that the search results _must_ be an entity of this type, e.g. [`Q5`](https://www.wikidata.org/wiki/Q5) for humans", + ], + [ + "notInstanceof", + "A list of Q-identifiers which indicates that the search results _must not_ be an entity of this type, e.g. [`Q79007`](https://www.wikidata.org/wiki/Q79007) to filter away all streets from the search results", + ], + ] + ), + ]), + ], + ] + ), + new Title("Example usage"), + `The following is the 'freeform'-part of a layer config which will trigger a search for the wikidata item corresponding with the name of the selected feature. It will also remove '-street', '-square', ... if found at the end of the name + +\`\`\`json +"freeform": { + "key": "name:etymology:wikidata", + "type": "wikidata", + "helperArgs": [ + "name", + { + "removePostfixes": {"en": [ + "street", + "boulevard", + "path", + "square", + "plaza", + ], + "nl": ["straat","plein","pad","weg",laan"], + "fr":["route (de|de la|de l'| de le)"] + }, + + "#": "Remove streets and parks from the search results:" + "notInstanceOf": ["Q79007","Q22698"] + } + + ] +} +\`\`\` + +Another example is to search for species and trees: + +\`\`\`json + "freeform": { + "key": "species:wikidata", + "type": "wikidata", + "helperArgs": [ + "species", + { + "instanceOf": [10884, 16521] + }] + } +\`\`\` +`, + ]) + constructor(options?: { searchText?: UIEventSource value?: UIEventSource diff --git a/test.ts b/test.ts index bed47cf8bb..0b18421a94 100644 --- a/test.ts +++ b/test.ts @@ -3,6 +3,12 @@ import * as theme from "./assets/generated/themes/shops.json" import ThemeViewState from "./Models/ThemeViewState" import Combine from "./UI/Base/Combine" import SpecialVisualizations from "./UI/SpecialVisualizations" +import InputHelpers from "./UI/InputElement/InputHelpers" +import BaseUIElement from "./UI/BaseUIElement" +import { UIEventSource } from "./Logic/UIEventSource" +import { VariableUiElement } from "./UI/Base/VariableUIElement" +import { FixedUiElement } from "./UI/Base/FixedUiElement" +import Title from "./UI/Base/Title" function testspecial() { const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) @@ -14,4 +20,26 @@ function testspecial() { new Combine(all).AttachTo("maindiv") } -testspecial() +function testinput() { + const els: BaseUIElement[] = [] + for (const key in InputHelpers.AvailableInputHelpers) { + const value = new UIEventSource(undefined) + const helper = InputHelpers.AvailableInputHelpers[key](value, { + mapProperties: { + zoom: new UIEventSource(16), + location: new UIEventSource({ lat: 51.1, lon: 3.2 }), + }, + }) + + els.push( + new Combine([ + new Title(key), + helper, + new VariableUiElement(value.map((v) => new FixedUiElement(v))), + ]).SetClass("flex flex-col p-1 border-3 border-gray-500") + ) + } + new Combine(els).SetClass("flex flex-col").AttachTo("maindiv") +} +testinput() +// testspecial() From 37ab1e57351f7d86c81802e5acee10e9a8d51fc0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 16 Apr 2023 03:56:37 +0200 Subject: [PATCH 042/257] Refactoring: remove obsolete doctests --- UI/Popup/TagRenderingQuestion.ts | 63 -------------------------------- 1 file changed, 63 deletions(-) diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index f2ea087f3f..296c451b66 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -317,69 +317,6 @@ export default class TagRenderingQuestion extends Combine { return values } - /** - * - * // Should return the search as freeform value - * const source = new UIEventSource({id: "1234"}) - * const tr = new TagRenderingConfig({ - * id:"test", - * render:"The value is {key}", - * freeform: { - * key:"key" - * }, - * mappings: [ - * { - * if:"x=y", - * then:"z", - * searchTerms: { - * "en" : ["z"] - * } - * } - * ] - * }, "test"); - * const selector = TagRenderingQuestion.GenerateSearchableSelector( - * undefined, - * tr, - * tr.mappings, - * source, - * { - * search: new UIEventSource("value") - * } - * ); - * selector.GetValue().data // => new And([new Tag("key","value")]) - * - * // Should return the search as freeform value, even if a previous search matched - * const source = new UIEventSource({id: "1234"}) - * const search = new UIEventSource("") - * const tr = new TagRenderingConfig({ - * id:"test", - * render:"The value is {key}", - * freeform: { - * key:"key" - * }, - * mappings: [ - * { - * if:"x=y", - * then:"z", - * searchTerms: { - * "en" : ["z"] - * } - * } - * ] - * }, "test"); - * const selector = TagRenderingQuestion.GenerateSearchableSelector( - * undefined, - * tr, - * tr.mappings, - * source, - * { - * search - * } - * ); - * search.setData("z") - * search.setData("zx") - * selector.GetValue().data // => new And([new Tag("key","zx")]) - */ private static GenerateSearchableSelector( state: FeaturePipelineState, configuration: TagRenderingConfig, From 9a282cbce48a6bb58ad699bfea6fb1a8a96cafef Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 16 Apr 2023 04:13:09 +0200 Subject: [PATCH 043/257] Refactoring(layout): move infobox to right modal; fix minimap --- Logic/GeoOperations.ts | 3 +- UI/Base/FloatOver.svelte | 2 +- UI/Base/ModalRight.svelte | 20 ++++ UI/Popup/MinimapViz.ts | 19 +++- UI/ThemeViewGUI.svelte | 5 +- css/index-tailwind-output.css | 202 +++++++++++----------------------- 6 files changed, 107 insertions(+), 144 deletions(-) create mode 100644 UI/Base/ModalRight.svelte diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 038b18c81e..c9af7c6e2a 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -4,6 +4,7 @@ import * as turf from "@turf/turf" import { AllGeoJSON, booleanWithin, Coord, Lines } from "@turf/turf" import { Feature, + FeatureCollection, GeoJSON, Geometry, LineString, @@ -243,7 +244,7 @@ export class GeoOperations { }) } - static bbox(feature: any): Feature { + static bbox(feature: Feature | FeatureCollection): Feature { const [lon, lat, lon0, lat0] = turf.bbox(feature) return { type: "Feature", diff --git a/UI/Base/FloatOver.svelte b/UI/Base/FloatOver.svelte index 934883a577..af28c54c5c 100644 --- a/UI/Base/FloatOver.svelte +++ b/UI/Base/FloatOver.svelte @@ -9,7 +9,7 @@
-
+
dispatch("close")}> diff --git a/UI/Base/ModalRight.svelte b/UI/Base/ModalRight.svelte new file mode 100644 index 0000000000..b0a11a3606 --- /dev/null +++ b/UI/Base/ModalRight.svelte @@ -0,0 +1,20 @@ + + +
+
+ +
dispatch("close")}> + +
+
+ +
+
diff --git a/UI/Popup/MinimapViz.ts b/UI/Popup/MinimapViz.ts index a497fb0263..c6ca940b0e 100644 --- a/UI/Popup/MinimapViz.ts +++ b/UI/Popup/MinimapViz.ts @@ -6,6 +6,9 @@ import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" import SvelteUIElement from "../Base/SvelteUIElement" import MaplibreMap from "../Map/MaplibreMap.svelte" import ShowDataLayer from "../Map/ShowDataLayer" +import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import { GeoOperations } from "../../Logic/GeoOperations" +import { BBox } from "../../Logic/BBox" export class MinimapViz implements SpecialVisualization { funcName = "minimap" @@ -27,7 +30,9 @@ export class MinimapViz implements SpecialVisualization { constr( state: SpecialVisualizationState, tagSource: UIEventSource>, - args: string[] + args: string[], + feature: Feature, + layer: LayerConfig ) { if (state === undefined) { return undefined @@ -77,6 +82,12 @@ export class MinimapViz implements SpecialVisualization { zoom = parsed } } + featuresToShow.addCallbackAndRunD((features) => { + const bboxGeojson = GeoOperations.bbox({ features, type: "FeatureCollection" }) + const [lon, lat] = GeoOperations.centerpointCoordinates(bboxGeojson) + mla.bounds.setData(BBox.get(bboxGeojson)) + mla.location.setData({ lon, lat }) + }) mla.zoom.setData(zoom) mla.allowMoving.setData(false) mla.allowZooming.setData(false) @@ -87,8 +98,8 @@ export class MinimapViz implements SpecialVisualization { state.layout.layers ) - return new SvelteUIElement(MaplibreMap, { map: mlmap }).SetStyle( - "overflow: hidden; pointer-events: none;" - ) + return new SvelteUIElement(MaplibreMap, { map: mlmap }) + .SetClass("h-40 rounded") + .SetStyle("overflow: hidden; pointer-events: none;") } } diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 9b66947ae0..02f64d1b67 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -30,6 +30,7 @@ import LoginButton from "./Base/LoginButton.svelte"; import CopyrightPanel from "./BigComponents/CopyrightPanel"; import { DownloadPanel } from "./BigComponents/DownloadPanel"; + import ModalRight from "./Base/ModalRight.svelte"; export let state: ThemeViewState; let layout = state.layout; @@ -99,10 +100,10 @@
{#if $selectedElement !== undefined && $selectedLayer !== undefined} - {selectedElement.setData(undefined)}}> + {selectedElement.setData(undefined)}}> - + {/if} diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 45090b5bfe..dbc66652c3 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -683,13 +683,6 @@ video { position: sticky; } -.inset-0 { - top: 0px; - right: 0px; - bottom: 0px; - left: 0px; -} - .left-24 { left: 6rem; } @@ -702,26 +695,6 @@ video { top: 14rem; } -.top-12 { - top: 3rem; -} - -.left-3 { - left: 0.75rem; -} - -.top-3 { - top: 0.75rem; -} - -.right-2 { - right: 0.5rem; -} - -.bottom-3 { - bottom: 0.75rem; -} - .top-2 { top: 0.5rem; } @@ -730,10 +703,6 @@ video { right: 0.75rem; } -.bottom-0 { - bottom: 0px; -} - .top-0 { top: 0px; } @@ -742,6 +711,10 @@ video { left: 0px; } +.bottom-0 { + bottom: 0px; +} + .right-0 { right: 0px; } @@ -843,6 +816,11 @@ video { margin-bottom: 0.5rem; } +.mx-1 { + margin-left: 0.25rem; + margin-right: 0.25rem; +} + .my-4 { margin-top: 1rem; margin-bottom: 1rem; @@ -863,6 +841,10 @@ video { margin-bottom: 0.75rem; } +.mb-4 { + margin-bottom: 1rem; +} + .mr-2 { margin-right: 0.5rem; } @@ -879,6 +861,10 @@ video { margin-top: 1rem; } +.mt-6 { + margin-top: 1.5rem; +} + .mt-2 { margin-top: 0.5rem; } @@ -887,18 +873,10 @@ video { margin-left: 0.5rem; } -.mb-4 { - margin-bottom: 1rem; -} - .ml-4 { margin-left: 1rem; } -.mt-6 { - margin-top: 1.5rem; -} - .mb-24 { margin-bottom: 6rem; } @@ -975,10 +953,6 @@ video { box-sizing: border-box; } -.box-content { - box-sizing: content-box; -} - .block { display: block; } @@ -1083,6 +1057,10 @@ video { height: 12rem; } +.h-40 { + height: 10rem; +} + .max-h-20vh { max-height: 20vh; } @@ -1095,10 +1073,6 @@ video { max-height: 1.75rem; } -.max-h-8 { - max-height: 2rem; -} - .max-h-24 { max-height: 6rem; } @@ -1107,14 +1081,6 @@ video { min-height: 8rem; } -.w-full { - width: 100%; -} - -.w-6 { - width: 1.5rem; -} - .w-8 { width: 2rem; } @@ -1135,6 +1101,14 @@ video { width: 3rem; } +.w-6 { + width: 1.5rem; +} + +.w-full { + width: 100%; +} + .w-screen { width: 100vw; } @@ -1389,6 +1363,14 @@ video { word-break: break-all; } +.rounded-xl { + border-radius: 0.75rem; +} + +.rounded-lg { + border-radius: 0.5rem; +} + .rounded-3xl { border-radius: 1.5rem; } @@ -1401,14 +1383,6 @@ video { border-radius: 9999px; } -.rounded-xl { - border-radius: 0.75rem; -} - -.rounded-lg { - border-radius: 0.5rem; -} - .rounded-md { border-radius: 0.375rem; } @@ -1462,6 +1436,11 @@ video { border-style: dotted; } +.border-gray-500 { + --tw-border-opacity: 1; + border-color: rgb(107 114 128 / var(--tw-border-opacity)); +} + .border-black { --tw-border-opacity: 1; border-color: rgb(0 0 0 / var(--tw-border-opacity)); @@ -1497,21 +1476,6 @@ video { border-color: rgb(59 130 246 / var(--tw-border-opacity)); } -.border-gray-200 { - --tw-border-opacity: 1; - border-color: rgb(229 231 235 / var(--tw-border-opacity)); -} - -.border-gray-500 { - --tw-border-opacity: 1; - border-color: rgb(107 114 128 / var(--tw-border-opacity)); -} - -.border-red-500 { - --tw-border-opacity: 1; - border-color: rgb(239 68 68 / var(--tw-border-opacity)); -} - .border-opacity-50 { --tw-border-opacity: 0.5; } @@ -1566,14 +1530,14 @@ video { background-color: rgb(254 202 202 / var(--tw-bg-opacity)); } -.p-4 { - padding: 1rem; -} - .p-1 { padding: 0.25rem; } +.p-4 { + padding: 1rem; +} + .p-2 { padding: 0.5rem; } @@ -1609,10 +1573,6 @@ video { padding-right: 1rem; } -.pl-3 { - padding-left: 0.75rem; -} - .pb-12 { padding-bottom: 3rem; } @@ -1669,6 +1629,10 @@ video { padding-right: 1rem; } +.pl-3 { + padding-left: 0.75rem; +} + .pl-1 { padding-left: 0.25rem; } @@ -1701,10 +1665,6 @@ video { text-align: justify; } -.align-baseline { - vertical-align: baseline; -} - .align-middle { vertical-align: middle; } @@ -1714,16 +1674,16 @@ video { line-height: 1.75rem; } -.text-lg { - font-size: 1.125rem; - line-height: 1.75rem; -} - .text-2xl { font-size: 1.5rem; line-height: 2rem; } +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + .text-4xl { font-size: 2.25rem; line-height: 2.5rem; @@ -2503,10 +2463,6 @@ input { } @media (min-width: 640px) { - .sm\:top-3 { - top: 0.75rem; - } - .sm\:m-6 { margin: 1.5rem; } @@ -2532,12 +2488,6 @@ input { height: 6rem; } - .sm\:w-fit { - width: -webkit-fit-content; - width: -moz-fit-content; - width: fit-content; - } - .sm\:w-24 { width: 6rem; } @@ -2566,30 +2516,10 @@ input { padding: 1.5rem; } - .sm\:p-0\.5 { - padding: 0.125rem; - } - - .sm\:p-1\.5 { - padding: 0.375rem; - } - - .sm\:p-0 { - padding: 0px; - } - - .sm\:p-1 { - padding: 0.25rem; - } - .sm\:p-2 { padding: 0.5rem; } - .sm\:pl-0 { - padding-left: 0px; - } - .sm\:pt-1 { padding-top: 0.25rem; } @@ -2659,12 +2589,12 @@ input { height: 3rem; } - .md\:w-1\/3 { + .md\:w-2\/6 { width: 33.333333%; } - .md\:w-2\/6 { - width: 33.333333%; + .md\:w-6\/12 { + width: 50%; } .md\:w-auto { @@ -2687,14 +2617,6 @@ input { border-top-width: 2px; } - .md\:p-1 { - padding: 0.25rem; - } - - .md\:p-2 { - padding: 0.5rem; - } - .md\:p-4 { padding: 1rem; } @@ -2753,6 +2675,10 @@ input { width: 16.666667%; } + .lg\:w-5\/12 { + width: 41.666667%; + } + .lg\:w-1\/3 { width: 33.333333%; } @@ -2770,4 +2696,8 @@ input { .xl\:inline { display: inline; } + + .xl\:w-4\/12 { + width: 33.333333%; + } } From f203a1158d50870398b9651524d3adf1e8aa7219 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 16 Apr 2023 04:26:50 +0200 Subject: [PATCH 044/257] Refactoring: fix usersettings vis --- Models/ThemeConfig/Conversion/PrepareLayer.ts | 2 +- UI/Popup/MinimapViz.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index 370464d19c..ad1ddae4d9 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -1144,7 +1144,7 @@ export class AddMiniMap extends DesugaringStep { } convert(layerConfig: LayerConfigJson, context: string): { result: LayerConfigJson } { - if (!layerConfig.tagRenderings) { + if (!layerConfig.tagRenderings || layerConfig.source === "special") { return { result: layerConfig } } const state = this._state diff --git a/UI/Popup/MinimapViz.ts b/UI/Popup/MinimapViz.ts index c6ca940b0e..7c6d67fda8 100644 --- a/UI/Popup/MinimapViz.ts +++ b/UI/Popup/MinimapViz.ts @@ -34,7 +34,7 @@ export class MinimapViz implements SpecialVisualization { feature: Feature, layer: LayerConfig ) { - if (state === undefined) { + if (state === undefined || feature === undefined || layer.source === undefined) { return undefined } const keys = [...args] @@ -83,6 +83,9 @@ export class MinimapViz implements SpecialVisualization { } } featuresToShow.addCallbackAndRunD((features) => { + if (features.length === 0) { + return + } const bboxGeojson = GeoOperations.bbox({ features, type: "FeatureCollection" }) const [lon, lat] = GeoOperations.centerpointCoordinates(bboxGeojson) mla.bounds.setData(BBox.get(bboxGeojson)) From e36e9123f36636795c2828a7e69521d89ce45df0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 18 Apr 2023 23:44:49 +0200 Subject: [PATCH 045/257] Refactoring: improve caching --- .../Actors/SaveFeatureSourceToLocalStorage.ts | 63 +++++++++++++++++-- .../FeatureSource/Actors/TileLocalStorage.ts | 26 +++++--- .../LocalStorageFeatureSource.ts | 9 ++- Logic/Web/IdbLocalStorage.ts | 6 +- Models/ThemeViewState.ts | 7 ++- 5 files changed, 91 insertions(+), 20 deletions(-) diff --git a/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts b/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts index 938c1ccac4..dbb4ae8559 100644 --- a/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts +++ b/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts @@ -2,8 +2,50 @@ import { FeatureSource } from "../FeatureSource" import { Feature } from "geojson" import TileLocalStorage from "./TileLocalStorage" import { GeoOperations } from "../../GeoOperations" +import FeaturePropertiesStore from "./FeaturePropertiesStore" +import { UIEventSource } from "../../UIEventSource" import { Utils } from "../../../Utils" +class SingleTileSaver { + private readonly _storage: UIEventSource + private readonly _registeredIds = new Set() + private readonly _featureProperties: FeaturePropertiesStore + private readonly _isDirty = new UIEventSource(false) + constructor( + storage: UIEventSource & { flush: () => void }, + featureProperties: FeaturePropertiesStore + ) { + this._storage = storage + this._featureProperties = featureProperties + this._isDirty.stabilized(1000).addCallbackD((isDirty) => { + if (!isDirty) { + return + } + // 'isDirty' is set when tags of some object have changed + storage.flush() + this._isDirty.setData(false) + }) + } + + public saveFeatures(features: Feature[]) { + if (Utils.sameList(features, this._storage.data)) { + return + } + for (const feature of features) { + const id = feature.properties.id + if (this._registeredIds.has(id)) { + continue + } + this._featureProperties.getStore(id)?.addCallbackAndRunD(() => { + this._isDirty.setData(true) + }) + this._registeredIds.add(id) + } + + this._storage.setData(features) + } +} + /*** * Saves all the features that are passed in to localstorage, so they can be retrieved on the next run * @@ -12,16 +54,27 @@ import { Utils } from "../../../Utils" * Also see the sibling class */ export default class SaveFeatureSourceToLocalStorage { - constructor(layername: string, zoomlevel: number, features: FeatureSource) { + constructor( + layername: string, + zoomlevel: number, + features: FeatureSource, + featureProperties: FeaturePropertiesStore + ) { const storage = TileLocalStorage.construct(layername) + const singleTileSavers: Map = new Map() features.features.addCallbackAndRunD((features) => { const sliced = GeoOperations.slice(zoomlevel, features) + sliced.forEach((features, tileIndex) => { - const src = storage.getTileSource(tileIndex) - if (Utils.sameList(src.data, features)) { - return + let tileSaver = singleTileSavers.get(tileIndex) + if (tileSaver === undefined) { + const src = storage.getTileSource(tileIndex) + tileSaver = new SingleTileSaver(src, featureProperties) + singleTileSavers.set(tileIndex, tileSaver) } - src.setData(features) + // Don't cache not-uploaded features yet - they'll be cached when the receive their id + features = features.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/)) + tileSaver.saveFeatures(features) }) }) } diff --git a/Logic/FeatureSource/Actors/TileLocalStorage.ts b/Logic/FeatureSource/Actors/TileLocalStorage.ts index 642110d4ee..9ee40c6034 100644 --- a/Logic/FeatureSource/Actors/TileLocalStorage.ts +++ b/Logic/FeatureSource/Actors/TileLocalStorage.ts @@ -4,12 +4,14 @@ import { UIEventSource } from "../../UIEventSource" /** * A class which allows to read/write a tile to local storage. * - * Does the heavy lifting for LocalStorageFeatureSource and SaveFeatureToLocalStorage + * Does the heavy lifting for LocalStorageFeatureSource and SaveFeatureToLocalStorage. + * + * Note: OSM-features with a negative id are ignored */ export default class TileLocalStorage { private static perLayer: Record> = {} private readonly _layername: string - private readonly cachedSources: Record> = {} + private readonly cachedSources: Record & { flush: () => void }> = {} private constructor(layername: string) { this._layername = layername @@ -27,24 +29,26 @@ export default class TileLocalStorage { } /** - * Constructs a UIEventSource element which is synced with localStorage - * @param layername - * @param tileIndex + * Constructs a UIEventSource element which is synced with localStorage. + * Supports 'flush' */ - public getTileSource(tileIndex: number): UIEventSource { + public getTileSource(tileIndex: number): UIEventSource & { flush: () => void } { const cached = this.cachedSources[tileIndex] if (cached) { return cached } - const src = UIEventSource.FromPromise(this.GetIdb(tileIndex)) + const src = & { flush: () => void }>( + UIEventSource.FromPromise(this.GetIdb(tileIndex)) + ) + src.flush = () => this.SetIdb(tileIndex, src.data) src.addCallbackD((data) => this.SetIdb(tileIndex, data)) this.cachedSources[tileIndex] = src return src } - private SetIdb(tileIndex: number, data): void { + private async SetIdb(tileIndex: number, data): Promise { try { - IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data) + await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data) } catch (e) { console.error( "Could not save tile to indexed-db: ", @@ -52,7 +56,9 @@ export default class TileLocalStorage { "tileIndex is:", tileIndex, "for layer", - this._layername + this._layername, + "data is", + data ) } } diff --git a/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts b/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts index 96bbb52755..972c48a8e7 100644 --- a/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts @@ -20,7 +20,14 @@ export default class LocalStorageFeatureSource extends DynamicTileSource { const storage = TileLocalStorage.construct(layername) super( zoomlevel, - (tileIndex) => new StaticFeatureSource(storage.getTileSource(tileIndex)), + (tileIndex) => + new StaticFeatureSource( + storage + .getTileSource(tileIndex) + .map((features) => + features?.filter((f) => !f.properties.id.match(/(node|way)\/-[0-9]+/)) + ) + ), mapProperties, options ) diff --git a/Logic/Web/IdbLocalStorage.ts b/Logic/Web/IdbLocalStorage.ts index 2d23db76ec..8abc2a9e5a 100644 --- a/Logic/Web/IdbLocalStorage.ts +++ b/Logic/Web/IdbLocalStorage.ts @@ -38,11 +38,11 @@ export class IdbLocalStorage { return src } - public static SetDirectly(key: string, value) { - idb.set(key, value) + public static SetDirectly(key: string, value): Promise { + return idb.set(key, value) } - static GetDirectly(key: string) { + static GetDirectly(key: string): Promise { return idb.get(key) } } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 82323baf57..7d668fdb73 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -158,7 +158,12 @@ export default class ThemeViewState implements SpecialVisualizationState { this.perLayer = perLayer.perLayer this.perLayer.forEach((fs) => { - new SaveFeatureSourceToLocalStorage(fs.layer.layerDef.id, 15, fs) + new SaveFeatureSourceToLocalStorage( + fs.layer.layerDef.id, + 15, + fs, + this.featureProperties + ) const filtered = new FilteringFeatureSource( fs.layer, From 7f8969146acba00e14687df8c20f858c42b93579 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 19 Apr 2023 03:20:49 +0200 Subject: [PATCH 046/257] Refactoring: allow to export the map as PNG --- Models/Constants.ts | 2 + Models/MapProperties.ts | 4 + Models/MenuState.ts | 16 ++- Models/ThemeConfig/LayerConfig.ts | 2 +- Models/ThemeConfig/PointRenderingConfig.ts | 21 +++- Models/ThemeViewState.ts | 6 +- UI/BigComponents/AllDownloads.ts | 8 +- UI/BigComponents/DownloadPanel.ts | 27 +++++ UI/BigComponents/LeftControls.ts | 14 +-- UI/Map/MapLibreAdaptor.ts | 129 ++++++++++++++++++--- UI/SpecialVisualization.ts | 4 +- Utils.ts | 1 + assets/tagRenderings/questions.json | 2 +- langs/en.json | 4 +- package-lock.json | 31 +++-- package.json | 2 + 16 files changed, 207 insertions(+), 66 deletions(-) diff --git a/Models/Constants.ts b/Models/Constants.ts index f30672b2b1..21bbe9223d 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -1,5 +1,7 @@ import { Utils } from "../Utils" +export type PriviligedLayerType = typeof Constants.priviliged_layers[number] + export default class Constants { public static vNumber = "0.30.0" diff --git a/Models/MapProperties.ts b/Models/MapProperties.ts index 6e6da6c1a2..4f5403998a 100644 --- a/Models/MapProperties.ts +++ b/Models/MapProperties.ts @@ -15,3 +15,7 @@ export interface MapProperties { readonly allowZooming: UIEventSource } + +export interface ExportableMap { + exportAsPng(): Promise +} diff --git a/Models/MenuState.ts b/Models/MenuState.ts index d613b00489..43d3146181 100644 --- a/Models/MenuState.ts +++ b/Models/MenuState.ts @@ -2,6 +2,7 @@ import LayerConfig from "./ThemeConfig/LayerConfig" import { UIEventSource } from "../Logic/UIEventSource" import UserRelatedState from "../Logic/State/UserRelatedState" import { Utils } from "../Utils" +import { LocalStorageSource } from "../Logic/Web/LocalStorageSource" /** * Indicates if a menu is open, and if so, which tab is selected; @@ -11,12 +12,12 @@ import { Utils } from "../Utils" */ export class MenuState { private static readonly _themeviewTabs = ["intro", "filters", "download", "copyright"] as const - public readonly themeIsOpened = new UIEventSource(true) + public readonly themeIsOpened: UIEventSource public readonly themeViewTabIndex: UIEventSource public readonly themeViewTab: UIEventSource private static readonly _menuviewTabs = ["about", "settings", "community", "privacy"] as const - public readonly menuIsOpened = new UIEventSource(false) + public readonly menuIsOpened: UIEventSource public readonly menuViewTabIndex: UIEventSource public readonly menuViewTab: UIEventSource @@ -24,15 +25,20 @@ export class MenuState { undefined ) public highlightedUserSetting: UIEventSource = new UIEventSource(undefined) - constructor() { - this.themeViewTabIndex = new UIEventSource(0) + constructor(themeid: string = "") { + if (themeid) { + themeid += "-" + } + this.themeIsOpened = LocalStorageSource.GetParsed(themeid + "thememenuisopened", true) + this.themeViewTabIndex = LocalStorageSource.GetParsed(themeid + "themeviewtabindex", 0) this.themeViewTab = this.themeViewTabIndex.sync( (i) => MenuState._themeviewTabs[i], [], (str) => MenuState._themeviewTabs.indexOf(str) ) - this.menuViewTabIndex = new UIEventSource(1) + this.menuIsOpened = LocalStorageSource.GetParsed(themeid + "menuisopened", false) + this.menuViewTabIndex = LocalStorageSource.GetParsed(themeid + "menuviewtabindex", 0) this.menuViewTab = this.menuViewTabIndex.sync( (i) => MenuState._menuviewTabs[i], [], diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index a8488b053f..13212878c7 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -424,7 +424,7 @@ export default class LayerConfig extends WithContextLoader { if (mapRendering === undefined) { return undefined } - return mapRendering.GetBaseIcon(this.GetBaseTags()) + return mapRendering.GetBaseIcon(this.GetBaseTags(), { noFullWidth: true }) } public GetBaseTags(): Record { diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts index bb6c5cce80..0c51b05394 100644 --- a/Models/ThemeConfig/PointRenderingConfig.ts +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -136,7 +136,10 @@ export default class PointRenderingConfig extends WithContextLoader { multiSpec: string, rotation: string, isBadge: boolean, - defaultElement: BaseUIElement = undefined + defaultElement: BaseUIElement = undefined, + options?: { + noFullWidth?: boolean + } ) { if (multiSpec === undefined) { return defaultElement @@ -150,11 +153,21 @@ export default class PointRenderingConfig extends WithContextLoader { if (elements.length === 0) { return defaultElement } else { - return new Combine(elements).SetClass("relative block w-full h-full") + const combine = new Combine(elements).SetClass("relative block") + if (options?.noFullWidth) { + return combine + } + combine.SetClass("w-full h-full") + return combine } } - public GetBaseIcon(tags?: Record): BaseUIElement { + public GetBaseIcon( + tags?: Record, + options?: { + noFullWidth?: boolean + } + ): BaseUIElement { tags = tags ?? { id: "node/-1" } let defaultPin: BaseUIElement = undefined if (this.label === undefined) { @@ -176,7 +189,7 @@ export default class PointRenderingConfig extends WithContextLoader { // This is probably already prepared HTML return new FixedUiElement(Utils.SubstituteKeys(htmlDefs, tags)) } - return PointRenderingConfig.FromHtmlMulti(htmlDefs, rotation, false, defaultPin) + return PointRenderingConfig.FromHtmlMulti(htmlDefs, rotation, false, defaultPin, options) } public GetSimpleIcon(tags: Store>): BaseUIElement { diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 7d668fdb73..75b59559d8 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -8,7 +8,7 @@ import { WritableFeatureSource, } from "../Logic/FeatureSource/FeatureSource" import { OsmConnection } from "../Logic/Osm/OsmConnection" -import { MapProperties } from "./MapProperties" +import { ExportableMap, MapProperties } from "./MapProperties" import LayerState from "../Logic/State/LayerState" import { Feature } from "geojson" import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" @@ -63,7 +63,7 @@ export default class ThemeViewState implements SpecialVisualizationState { readonly osmConnection: OsmConnection readonly selectedElement: UIEventSource - readonly mapProperties: MapProperties + readonly mapProperties: MapProperties & ExportableMap readonly dataIsLoading: Store // TODO readonly guistate: MenuState @@ -82,7 +82,7 @@ export default class ThemeViewState implements SpecialVisualizationState { readonly lastClickObject: WritableFeatureSource constructor(layout: LayoutConfig) { this.layout = layout - this.guistate = new MenuState() + this.guistate = new MenuState(layout.id) this.map = new UIEventSource(undefined) const initial = new InitialMapPositioning(layout) this.mapProperties = new MapLibreAdaptor(this.map, initial) diff --git a/UI/BigComponents/AllDownloads.ts b/UI/BigComponents/AllDownloads.ts index 09c2e33dfe..d8166e2d94 100644 --- a/UI/BigComponents/AllDownloads.ts +++ b/UI/BigComponents/AllDownloads.ts @@ -86,12 +86,6 @@ export default class AllDownloads extends ScrollableFullScreen { state.featureSwitchExportAsPdf ) - const exportPanel = new Toggle( - new DownloadPanel(state), - undefined, - state.featureSwitchEnableExport - ) - return new Combine([pdf, exportPanel]).SetClass("flex flex-col") - } + return pdf } diff --git a/UI/BigComponents/DownloadPanel.ts b/UI/BigComponents/DownloadPanel.ts index d46e0ab03f..2cb856c8cc 100644 --- a/UI/BigComponents/DownloadPanel.ts +++ b/UI/BigComponents/DownloadPanel.ts @@ -17,6 +17,7 @@ import { SpecialVisualizationState } from "../SpecialVisualization" import { Feature, FeatureCollection } from "geojson" import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore" import LayerState from "../../Logic/State/LayerState" +import { PriviligedLayerType } from "../../Models/Constants" export class DownloadPanel extends Toggle { constructor(state: SpecialVisualizationState) { @@ -86,11 +87,37 @@ export class DownloadPanel extends Toggle { ) }) + const buttonPng = new SubtleButton( + Svg.floppy_ui(), + new Combine([t.downloadAsPng.SetClass("font-bold"), t.downloadAsPngHelper]) + ).OnClickWithLoading(t.exporting, async () => { + const gpsLayer = state.layerState.filteredLayers.get( + "gps_location" + ) + const gpsIsDisplayed = gpsLayer.isDisplayed.data + try { + gpsLayer.isDisplayed.setData(false) + const png = await state.mapProperties.exportAsPng() + Utils.offerContentsAsDownloadableFile( + png, + `MapComplete_${name}_export_${new Date().toISOString().substr(0, 19)}.png`, + { + mimetype: "image/png", + } + ) + } catch (e) { + console.error(e) + } finally { + gpsLayer.isDisplayed.setData(gpsIsDisplayed) + } + }) + const downloadButtons = new Combine([ new Title(t.title), buttonGeoJson, buttonCSV, buttonSvg, + buttonPng, includeMetaToggle, t.licenseInfo.SetClass("link-underline"), ]).SetClass("w-full flex flex-col") diff --git a/UI/BigComponents/LeftControls.ts b/UI/BigComponents/LeftControls.ts index 3faf39937e..4021c23d8d 100644 --- a/UI/BigComponents/LeftControls.ts +++ b/UI/BigComponents/LeftControls.ts @@ -56,20 +56,10 @@ export default class LeftControls extends Combine { ) new AllDownloads(guiState.downloadControlIsOpened, state) - const toggledDownload = new MapControlButton(Svg.download_svg()).onClick(() => - guiState.downloadControlIsOpened.setData(true) - ) - const downloadButton = new Toggle( - toggledDownload, - undefined, - state.featureSwitchEnableExport.map( - (downloadEnabled) => downloadEnabled || state.featureSwitchExportAsPdf.data, - [state.featureSwitchExportAsPdf] - ) - ) - super([currentViewAction, downloadButton]) + + super([currentViewAction]) this.SetClass("flex flex-col") } diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index 74a9aa0f95..4b28bdea75 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -4,14 +4,15 @@ import { Map as MlMap } from "maplibre-gl" import { RasterLayerPolygon, RasterLayerProperties } from "../../Models/RasterLayers" import { Utils } from "../../Utils" import { BBox } from "../../Logic/BBox" -import { MapProperties } from "../../Models/MapProperties" +import { ExportableMap, MapProperties } from "../../Models/MapProperties" import SvelteUIElement from "../Base/SvelteUIElement" import MaplibreMap from "./MaplibreMap.svelte" +import html2canvas from "html2canvas" /** * The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties` */ -export class MapLibreAdaptor implements MapProperties { +export class MapLibreAdaptor implements MapProperties, ExportableMap { private static maplibre_control_handlers = [ // "scrollZoom", // "boxZoom", @@ -125,23 +126,6 @@ export class MapLibreAdaptor implements MapProperties { this.bounds.addCallbackAndRunD((bounds) => self.setBounds(bounds)) } - private updateStores() { - const map = this._maplibreMap.data - if (map === undefined) { - return - } - const dt = this.location.data - dt.lon = map.getCenter().lng - dt.lat = map.getCenter().lat - this.location.ping() - this.zoom.setData(Math.round(map.getZoom() * 10) / 10) - const bounds = map.getBounds() - const bbox = new BBox([ - [bounds.getEast(), bounds.getNorth()], - [bounds.getWest(), bounds.getSouth()], - ]) - this.bounds.setData(bbox) - } /** * Convenience constructor */ @@ -189,6 +173,113 @@ export class MapLibreAdaptor implements MapProperties { return url } + async exportAsPng(): Promise { + const map = this._maplibreMap.data + if (map === undefined) { + return undefined + } + + function setDPI(canvas, dpi) { + // Set up CSS size. + canvas.style.width = canvas.style.width || canvas.width + "px" + canvas.style.height = canvas.style.height || canvas.height + "px" + + // Resize canvas and scale future draws. + const scaleFactor = dpi / 96 + canvas.width = Math.ceil(canvas.width * scaleFactor) + canvas.height = Math.ceil(canvas.height * scaleFactor) + const ctx = canvas.getContext("2d") + ctx?.scale(scaleFactor, scaleFactor) + } + + // Total hack - see https://stackoverflow.com/questions/42483449/mapbox-gl-js-export-map-to-png-or-pdf + + const drawOn = document.createElement("canvas") + drawOn.width = document.documentElement.clientWidth + drawOn.height = document.documentElement.clientHeight + + setDPI(drawOn, 4 * 96) + + const destinationCtx = drawOn.getContext("2d") + { + // First, we draw the maplibre-map onto the canvas. This does not export markers + // Inspiration by https://github.com/mapbox/mapbox-gl-js/issues/2766 + + const promise = new Promise((resolve) => { + map.once("render", () => { + destinationCtx.drawImage(map.getCanvas(), 0, 0) + resolve() + }) + }) + + while (!map.isStyleLoaded()) { + console.log("Waiting to fully load the style...") + await Utils.waitFor(100) + } + map.triggerRepaint() + await promise + // Reset the canvas width and height + map.resize() + } + { + // now, we draw the markers on top of the map + + /* We use html2canvas for this, but disable the map canvas object itself: + * it cannot deal with this canvas object. + * + * We also have to patch up a few more objects + * */ + const container = map.getCanvasContainer() + const origHeight = container.style.height + const origStyle = map.getCanvas().style.display + try { + map.getCanvas().style.display = "none" + if (!container.style.height) { + container.style.height = document.documentElement.clientHeight + "px" + } + + const markerCanvas: HTMLCanvasElement = await html2canvas( + map.getCanvasContainer(), + { + backgroundColor: "#00000000", + canvas: drawOn, + } + ) + const markers = await new Promise((resolve) => + markerCanvas.toBlob((data) => resolve(data)) + ) + console.log("Markers:", markers, markerCanvas) + // destinationCtx.drawImage(markerCanvas, 0, 0) + } catch (e) { + console.error(e) + } finally { + map.getCanvas().style.display = origStyle + container.style.height = origHeight + } + } + + // At last, we return the actual blob + return new Promise((resolve) => drawOn.toBlob((data) => resolve(data))) + } + + private updateStores() { + const map = this._maplibreMap.data + if (map === undefined) { + return + } + const dt = this.location.data + dt.lon = map.getCenter().lng + dt.lat = map.getCenter().lat + this.location.ping() + this.zoom.setData(Math.round(map.getZoom() * 10) / 10) + const bounds = map.getBounds() + const bbox = new BBox([ + [bounds.getEast(), bounds.getNorth()], + [bounds.getWest(), bounds.getSouth()], + ]) + this.bounds.setData(bbox) + } + private SetZoom(z: number) { const map = this._maplibreMap.data if (!map || z === undefined) { diff --git a/UI/SpecialVisualization.ts b/UI/SpecialVisualization.ts index dd0d6db17b..f249dfc10c 100644 --- a/UI/SpecialVisualization.ts +++ b/UI/SpecialVisualization.ts @@ -4,7 +4,7 @@ import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" import { OsmConnection } from "../Logic/Osm/OsmConnection" import { Changes } from "../Logic/Osm/Changes" -import { MapProperties } from "../Models/MapProperties" +import { ExportableMap, MapProperties } from "../Models/MapProperties" import LayerState from "../Logic/State/LayerState" import { Feature, Geometry } from "geojson" import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" @@ -42,7 +42,7 @@ export interface SpecialVisualizationState { /** * State of the main map */ - readonly mapProperties: MapProperties + readonly mapProperties: MapProperties & ExportableMap readonly selectedElement: UIEventSource /** diff --git a/Utils.ts b/Utils.ts index 4086627e35..9917f42eee 100644 --- a/Utils.ts +++ b/Utils.ts @@ -1045,6 +1045,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | "application/vnd.geo+json" | "{gpx=application/gpx+xml}" | "application/json" + | "image/png" } ) { const element = document.createElement("a") diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index 5b9cc48fd8..f3ce9db0cb 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -2058,4 +2058,4 @@ } ] } -} +} \ No newline at end of file diff --git a/langs/en.json b/langs/en.json index 3d0f1f6732..e7212d6518 100644 --- a/langs/en.json +++ b/langs/en.json @@ -159,8 +159,10 @@ "download": { "downloadAsPdf": "Download a PDF of the current map", "downloadAsPdfHelper": "Ideal to print the current map", + "downloadAsPng": "Download as image", + "downloadAsPngHelper": "Ideal to include in reports", "downloadAsSvg": "Download an SVG of the current map", - "downloadAsSvgHelper": "Compatible Inkscape or Adobe Illustrator; will need further processing ", + "downloadAsSvgHelper": "Compatible Inkscape or Adobe Illustrator; will need further processing", "downloadCSV": "Download visible data as CSV", "downloadCSVHelper": "Compatible with LibreOffice Calc, Excel, …", "downloadFeatureAsGeojson": "Download as GeoJSON-file", diff --git a/package-lock.json b/package-lock.json index 6eff6d6d43..ca43ed106b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@turf/distance": "^6.5.0", "@turf/length": "^6.5.0", "@turf/turf": "^6.5.0", + "@types/html2canvas": "^1.0.0", "@types/showdown": "^2.0.0", "chart.js": "^3.8.0", "country-language": "^0.1.7", @@ -29,6 +30,7 @@ "fake-dom": "^1.0.4", "geojson2svg": "^1.3.3", "html-to-markdown": "^1.0.0", + "html2canvas": "^1.4.1", "i18next-client": "^1.11.4", "idb-keyval": "^6.0.3", "jest-mock": "^29.4.1", @@ -3644,6 +3646,15 @@ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" }, + "node_modules/@types/html2canvas": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/html2canvas/-/html2canvas-1.0.0.tgz", + "integrity": "sha512-BJpVf+FIN9UERmzhbtUgpXj6XBZpG67FMgBLLoj9HZKd9XifcCpSV+UnFcwTZfEyun4U/KmCrrVOG7829L589w==", + "deprecated": "This is a stub types definition. html2canvas provides its own type definitions, so you do not need this installed.", + "dependencies": { + "html2canvas": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -4223,7 +4234,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", - "optional": true, "engines": { "node": ">= 0.6.0" } @@ -4876,7 +4886,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", - "optional": true, "dependencies": { "utrie": "^1.0.2" } @@ -6480,7 +6489,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", - "optional": true, "dependencies": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" @@ -10167,7 +10175,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", - "optional": true, "dependencies": { "utrie": "^1.0.2" } @@ -11527,7 +11534,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", - "optional": true, "dependencies": { "base64-arraybuffer": "^1.0.2" } @@ -14804,6 +14810,14 @@ "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" }, + "@types/html2canvas": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/html2canvas/-/html2canvas-1.0.0.tgz", + "integrity": "sha512-BJpVf+FIN9UERmzhbtUgpXj6XBZpG67FMgBLLoj9HZKd9XifcCpSV+UnFcwTZfEyun4U/KmCrrVOG7829L589w==", + "requires": { + "html2canvas": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -15288,8 +15302,7 @@ "base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", - "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", - "optional": true + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" }, "base64-js": { "version": "1.5.1", @@ -15763,7 +15776,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", - "optional": true, "requires": { "utrie": "^1.0.2" } @@ -16991,7 +17003,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", - "optional": true, "requires": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" @@ -19717,7 +19728,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", - "optional": true, "requires": { "utrie": "^1.0.2" } @@ -20828,7 +20838,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", - "optional": true, "requires": { "base64-arraybuffer": "^1.0.2" } diff --git a/package.json b/package.json index 5926bd549e..2aff8cc1ac 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "@turf/distance": "^6.5.0", "@turf/length": "^6.5.0", "@turf/turf": "^6.5.0", + "@types/html2canvas": "^1.0.0", "@types/showdown": "^2.0.0", "chart.js": "^3.8.0", "country-language": "^0.1.7", @@ -81,6 +82,7 @@ "fake-dom": "^1.0.4", "geojson2svg": "^1.3.3", "html-to-markdown": "^1.0.0", + "html2canvas": "^1.4.1", "i18next-client": "^1.11.4", "idb-keyval": "^6.0.3", "jest-mock": "^29.4.1", From 840990c08bbc863c19195114e79d509bc306ccd3 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 19 Apr 2023 13:23:46 +0200 Subject: [PATCH 047/257] Docs: add description again to individual tagrenderings --- Docs/BuiltinIndex.md | 2 + Docs/BuiltinLayers.md | 50 ++--------- Docs/Layers/address.md | 4 + Docs/Layers/advertising.md | 10 ++- Docs/Layers/all_streets.md | 6 ++ Docs/Layers/ambulancestation.md | 6 ++ Docs/Layers/artwork.md | 9 ++ Docs/Layers/atm.md | 6 ++ Docs/Layers/bank.md | 4 + Docs/Layers/banks_with_atm.md | 4 + Docs/Layers/barrier.md | 6 ++ Docs/Layers/bench.md | 6 ++ Docs/Layers/bench_at_pt.md | 6 ++ Docs/Layers/bicycle_library.md | 6 ++ Docs/Layers/bicycle_rental.md | 6 ++ Docs/Layers/bicycle_rental_non_docking.md | 6 ++ Docs/Layers/bicycle_tube_vending_machine.md | 6 ++ Docs/Layers/bike_cafe.md | 6 ++ Docs/Layers/bike_cleaning.md | 6 ++ Docs/Layers/bike_parking.md | 6 ++ Docs/Layers/bike_repair_station.md | 6 ++ Docs/Layers/bike_shop.md | 6 ++ Docs/Layers/bike_themed_object.md | 6 ++ Docs/Layers/binocular.md | 6 ++ Docs/Layers/birdhide.md | 6 ++ Docs/Layers/cafe_pub.md | 8 ++ Docs/Layers/car_rental.md | 6 ++ Docs/Layers/caravansites.md | 10 +++ Docs/Layers/charging_station.md | 6 ++ Docs/Layers/charging_station_ebikes.md | 6 ++ Docs/Layers/climbing.md | 4 + Docs/Layers/climbing_area.md | 4 + Docs/Layers/climbing_club.md | 4 + Docs/Layers/climbing_gym.md | 6 ++ Docs/Layers/climbing_opportunity.md | 4 + Docs/Layers/climbing_route.md | 6 ++ Docs/Layers/clock.md | 6 ++ Docs/Layers/crab_address.md | 4 + Docs/Layers/crossings.md | 6 ++ .../cultural_places_without_etymology.md | 4 + Docs/Layers/cycleways_and_roads.md | 4 + Docs/Layers/defibrillator.md | 6 ++ Docs/Layers/dentist.md | 6 ++ Docs/Layers/direction.md | 4 + Docs/Layers/doctors.md | 6 ++ Docs/Layers/dogfoodb.md | 8 ++ Docs/Layers/dogpark.md | 8 ++ Docs/Layers/dogshop.md | 10 +++ Docs/Layers/drinking_water.md | 6 ++ Docs/Layers/dumpstations.md | 6 ++ ...ducation_institutions_without_etymology.md | 4 + Docs/Layers/elevator.md | 8 ++ Docs/Layers/entrance.md | 6 ++ Docs/Layers/etymology.md | 4 + Docs/Layers/extinguisher.md | 6 ++ Docs/Layers/facadegardens.md | 6 ++ Docs/Layers/fietsstraat.md | 6 ++ Docs/Layers/fire_station.md | 6 ++ Docs/Layers/fitness_centre.md | 8 ++ Docs/Layers/fitness_station.md | 6 ++ Docs/Layers/food.md | 8 ++ Docs/Layers/friture.md | 8 ++ Docs/Layers/ghost_bike.md | 6 ++ Docs/Layers/governments.md | 6 ++ Docs/Layers/hackerspace.md | 90 ++++++++++++++++--- ...lth_and_social_places_without_etymology.md | 4 + Docs/Layers/hospital.md | 4 + Docs/Layers/hotel.md | 8 ++ Docs/Layers/hydrant.md | 6 ++ Docs/Layers/indoors.md | 6 ++ Docs/Layers/information_board.md | 6 ++ Docs/Layers/kerbs.md | 6 ++ Docs/Layers/kindergarten_childcare.md | 4 + Docs/Layers/lit_streets.md | 6 ++ Docs/Layers/map.md | 6 ++ Docs/Layers/maproulette.md | 4 + Docs/Layers/maproulette_challenge.md | 4 + Docs/Layers/maxspeed.md | 4 + Docs/Layers/medical-shops.md | 10 +++ Docs/Layers/nature_reserve.md | 8 ++ Docs/Layers/note.md | 4 + Docs/Layers/observation_tower.md | 8 ++ Docs/Layers/osm_community_index.md | 4 + Docs/Layers/parcel_lockers.md | 6 ++ Docs/Layers/parking.md | 6 ++ Docs/Layers/parking_spaces.md | 6 ++ Docs/Layers/parking_ticket_machine.md | 6 ++ .../parks_and_forests_without_etymology.md | 4 + Docs/Layers/pharmacy.md | 6 ++ Docs/Layers/physiotherapist.md | 6 ++ Docs/Layers/picnic_table.md | 6 ++ Docs/Layers/play_forest.md | 8 ++ Docs/Layers/playground.md | 8 ++ Docs/Layers/postboxes.md | 4 + Docs/Layers/postoffices.md | 4 + Docs/Layers/public_bookcase.md | 6 ++ Docs/Layers/railway_platforms.md | 4 + Docs/Layers/rainbow_crossing_high_zoom.md | 6 ++ Docs/Layers/rainbow_crossings.md | 6 ++ Docs/Layers/reception_desk.md | 8 ++ Docs/Layers/recycling.md | 6 ++ Docs/Layers/school.md | 4 + Docs/Layers/shelter.md | 4 + Docs/Layers/shops.md | 10 +++ Docs/Layers/slow_roads.md | 6 ++ Docs/Layers/speed_camera.md | 4 + Docs/Layers/speed_display.md | 4 + Docs/Layers/sport_pitch.md | 8 ++ Docs/Layers/sport_places_without_etymology.md | 4 + Docs/Layers/sport_shops.md | 10 +++ Docs/Layers/sports_centre.md | 6 ++ Docs/Layers/stairs.md | 6 ++ Docs/Layers/street_lamps.md | 6 ++ Docs/Layers/streets_without_etymology.md | 4 + Docs/Layers/surveillance_camera.md | 6 ++ Docs/Layers/tertiary_education.md | 4 + Docs/Layers/ticket_machine.md | 6 ++ Docs/Layers/ticket_validator.md | 6 ++ Docs/Layers/toekomstige_fietsstraat.md | 6 ++ Docs/Layers/toilet.md | 6 ++ Docs/Layers/toilet_at_amenity.md | 6 ++ .../toursistic_places_without_etymology.md | 4 + Docs/Layers/trail.md | 6 ++ Docs/Layers/transit_routes.md | 4 + Docs/Layers/transit_stops.md | 6 ++ Docs/Layers/tree_node.md | 6 ++ Docs/Layers/veterinary.md | 6 ++ Docs/Layers/viewpoint.md | 6 ++ Docs/Layers/village_green.md | 6 ++ Docs/Layers/visitor_information_centre.md | 4 + Docs/Layers/walls_and_buildings.md | 6 ++ Docs/Layers/waste_basket.md | 6 ++ Docs/Layers/waste_disposal.md | 6 ++ Docs/Layers/windturbine.md | 6 ++ Docs/SpecialInputElements.md | 6 +- Docs/TagInfo/mapcomplete_hackerspaces.json | 29 ++++++ Docs/TagInfo/mapcomplete_personal.json | 29 ++++++ Docs/Themes/artwork.md | 1 + scripts/generateLayerOverview.ts | 1 - 139 files changed, 920 insertions(+), 59 deletions(-) diff --git a/Docs/BuiltinIndex.md b/Docs/BuiltinIndex.md index d90b346710..b33c371116 100644 --- a/Docs/BuiltinIndex.md +++ b/Docs/BuiltinIndex.md @@ -425,6 +425,7 @@ - entrance - fitness_centre - food + - hackerspace - parking - picnic_table - railway_platforms @@ -718,6 +719,7 @@ - etymology + - hackerspace - play_forest - playground - shops diff --git a/Docs/BuiltinLayers.md b/Docs/BuiltinLayers.md index c1ed07dddd..38823f44f9 100644 --- a/Docs/BuiltinLayers.md +++ b/Docs/BuiltinLayers.md @@ -40,7 +40,6 @@ + [add_new](#add_new) + [add_note](#add_note) + [leftover-questions](#leftover-questions) - + [minimap](#minimap) * [Filters](#filters) 1. [conflation](#conflation) - [Basic tags for this layer](#basic-tags-for-this-layer) @@ -53,7 +52,6 @@ - [Basic tags for this layer](#basic-tags-for-this-layer) - [Supported attributes](#supported-attributes) + [leftover-questions](#leftover-questions) - + [minimap](#minimap) 1. [matchpoint](#matchpoint) - [Basic tags for this layer](#basic-tags-for-this-layer) - [Supported attributes](#supported-attributes) @@ -62,7 +60,6 @@ - [Supported attributes](#supported-attributes) + [all_tags](#all_tags) + [leftover-questions](#leftover-questions) - + [minimap](#minimap) 1. [usersettings](#usersettings) - [Basic tags for this layer](#basic-tags-for-this-layer) - [Supported attributes](#supported-attributes) @@ -85,7 +82,6 @@ + [show_debug](#show_debug) + [debug](#debug) + [leftover-questions](#leftover-questions) - + [minimap](#minimap) 1. [Normal layers](#normal-layers) @@ -349,6 +345,8 @@ This tagrendering has no question and is thus read-only +Shows a button to export this feature as GPX. Especially useful for route relations + This tagrendering has no question and is thus read-only @@ -359,6 +357,8 @@ This tagrendering has no question and is thus read-only +Shows a button to export this feature as geojson. Especially useful for debugging or using this in other programs + This tagrendering has no question and is thus read-only @@ -379,6 +379,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -525,16 +527,6 @@ This tagrendering has no question and is thus read-only -### minimap - - - -This tagrendering has no question and is thus read-only - - - - - #### Filters @@ -697,16 +689,6 @@ This tagrendering has no question and is thus read-only -### minimap - - - -This tagrendering has no question and is thus read-only - - - - - matchpoint ============ @@ -809,16 +791,6 @@ This tagrendering has no question and is thus read-only -### minimap - - - -This tagrendering has no question and is thus read-only - - - - - usersettings ============== @@ -1142,16 +1114,6 @@ This tagrendering has no question and is thus read-only -### minimap - - - -This tagrendering has no question and is thus read-only - - - - - Normal layers =============== diff --git a/Docs/Layers/address.md b/Docs/Layers/address.md index 98270cfecc..e4c403e682 100644 --- a/Docs/Layers/address.md +++ b/Docs/Layers/address.md @@ -129,6 +129,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -139,6 +141,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/advertising.md b/Docs/Layers/advertising.md index cbc385d3fa..2f889df5ef 100644 --- a/Docs/Layers/advertising.md +++ b/Docs/Layers/advertising.md @@ -78,6 +78,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -228,6 +230,9 @@ This rendering asks information about the property [ref](https://wiki.openstree This is rendered with `Reference number is {ref}` + + + ### leftover-questions @@ -242,6 +247,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -282,11 +289,12 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only This tagrendering is only visible in the popup if the following condition is met: `_last_edit:contributor~.+&_last_edit:changeset~.+` - This document is autogenerated from [assets/layers/advertising/advertising.json](https://github.com/pietervdvn/MapComplete/blob/develop/assets/layers/advertising/advertising.json) diff --git a/Docs/Layers/all_streets.md b/Docs/Layers/all_streets.md index 7bf1840513..2a655cf6cb 100644 --- a/Docs/Layers/all_streets.md +++ b/Docs/Layers/all_streets.md @@ -70,6 +70,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -90,6 +92,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -100,6 +104,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/ambulancestation.md b/Docs/Layers/ambulancestation.md index 80103dc295..93cd652b46 100644 --- a/Docs/Layers/ambulancestation.md +++ b/Docs/Layers/ambulancestation.md @@ -156,6 +156,8 @@ This is rendered with `The operator is a(n) {operator:type} entity.` +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -176,6 +178,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -186,6 +190,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/artwork.md b/Docs/Layers/artwork.md index b5fbd2a055..ce25c50cf2 100644 --- a/Docs/Layers/artwork.md +++ b/Docs/Layers/artwork.md @@ -15,6 +15,7 @@ An open map of statues, busts, graffitis and other artwork all over the world - This layer is shown at zoomlevel **12** and higher + - This layer will automatically load [walls_and_buildings](./walls_and_buildings.md) into the layout as it depends on it: a preset snaps to this layer (presets[1]) @@ -83,6 +84,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -174,6 +177,8 @@ This tagrendering has labels `artwork-question` +Shows a wikipedia box with the corresponding wikipedia article; the wikidata-item link can be changed by a contributor + The question is *What is the corresponding Wikidata entity?* This rendering asks information about the property [wikidata](https://wiki.openstreetmap.org/wiki/Key:wikidata) @@ -430,6 +435,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -470,6 +477,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/atm.md b/Docs/Layers/atm.md index f61f0a87f6..7327b23175 100644 --- a/Docs/Layers/atm.md +++ b/Docs/Layers/atm.md @@ -74,6 +74,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -212,6 +214,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -252,6 +256,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/bank.md b/Docs/Layers/bank.md index ed5dd64f11..cb1bc1bf25 100644 --- a/Docs/Layers/bank.md +++ b/Docs/Layers/bank.md @@ -96,6 +96,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -106,6 +108,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/banks_with_atm.md b/Docs/Layers/banks_with_atm.md index 9a88b41a9c..0253c65f5f 100644 --- a/Docs/Layers/banks_with_atm.md +++ b/Docs/Layers/banks_with_atm.md @@ -97,6 +97,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -107,6 +109,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/barrier.md b/Docs/Layers/barrier.md index 60abb5193f..0662050d7a 100644 --- a/Docs/Layers/barrier.md +++ b/Docs/Layers/barrier.md @@ -78,6 +78,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -237,6 +239,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -267,6 +271,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/bench.md b/Docs/Layers/bench.md index f883f0e8e6..73b1177de9 100644 --- a/Docs/Layers/bench.md +++ b/Docs/Layers/bench.md @@ -83,6 +83,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -400,6 +402,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -440,6 +444,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/bench_at_pt.md b/Docs/Layers/bench_at_pt.md index 5d1235ff29..bbca216cda 100644 --- a/Docs/Layers/bench_at_pt.md +++ b/Docs/Layers/bench_at_pt.md @@ -71,6 +71,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -122,6 +124,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -152,6 +156,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/bicycle_library.md b/Docs/Layers/bicycle_library.md index b79a9492c5..f016f4d5db 100644 --- a/Docs/Layers/bicycle_library.md +++ b/Docs/Layers/bicycle_library.md @@ -77,6 +77,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -236,6 +238,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -266,6 +270,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/bicycle_rental.md b/Docs/Layers/bicycle_rental.md index 99e92452a2..455ba5e36c 100644 --- a/Docs/Layers/bicycle_rental.md +++ b/Docs/Layers/bicycle_rental.md @@ -81,6 +81,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -395,6 +397,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -435,6 +439,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/bicycle_rental_non_docking.md b/Docs/Layers/bicycle_rental_non_docking.md index ec48f1214e..21deafebf8 100644 --- a/Docs/Layers/bicycle_rental_non_docking.md +++ b/Docs/Layers/bicycle_rental_non_docking.md @@ -80,6 +80,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -394,6 +396,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -434,6 +438,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/bicycle_tube_vending_machine.md b/Docs/Layers/bicycle_tube_vending_machine.md index 2b3d58bca2..6e0ed728fd 100644 --- a/Docs/Layers/bicycle_tube_vending_machine.md +++ b/Docs/Layers/bicycle_tube_vending_machine.md @@ -73,6 +73,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -212,6 +214,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -252,6 +256,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/bike_cafe.md b/Docs/Layers/bike_cafe.md index 3a0ec35ba3..28d9f2288a 100644 --- a/Docs/Layers/bike_cafe.md +++ b/Docs/Layers/bike_cafe.md @@ -77,6 +77,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -233,6 +235,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -243,6 +247,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/bike_cleaning.md b/Docs/Layers/bike_cleaning.md index bb036c4184..10071ef25b 100644 --- a/Docs/Layers/bike_cleaning.md +++ b/Docs/Layers/bike_cleaning.md @@ -70,6 +70,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -133,6 +135,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -173,6 +177,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/bike_parking.md b/Docs/Layers/bike_parking.md index f2bacddbf0..1af4be6be7 100644 --- a/Docs/Layers/bike_parking.md +++ b/Docs/Layers/bike_parking.md @@ -76,6 +76,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -225,6 +227,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -265,6 +269,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/bike_repair_station.md b/Docs/Layers/bike_repair_station.md index 212c675b7d..17aca7bf20 100644 --- a/Docs/Layers/bike_repair_station.md +++ b/Docs/Layers/bike_repair_station.md @@ -81,6 +81,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -355,6 +357,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -395,6 +399,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/bike_shop.md b/Docs/Layers/bike_shop.md index 665c6aafe7..9ebb6cbf98 100644 --- a/Docs/Layers/bike_shop.md +++ b/Docs/Layers/bike_shop.md @@ -92,6 +92,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -540,6 +542,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -560,6 +564,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/bike_themed_object.md b/Docs/Layers/bike_themed_object.md index 5d614c1d78..c91bf4e931 100644 --- a/Docs/Layers/bike_themed_object.md +++ b/Docs/Layers/bike_themed_object.md @@ -73,6 +73,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -181,6 +183,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -191,6 +195,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/binocular.md b/Docs/Layers/binocular.md index 8e0196ea10..2331a9385e 100644 --- a/Docs/Layers/binocular.md +++ b/Docs/Layers/binocular.md @@ -70,6 +70,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -123,6 +125,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -163,6 +167,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/birdhide.md b/Docs/Layers/birdhide.md index 6cd86a4b59..1b53e4fdfb 100644 --- a/Docs/Layers/birdhide.md +++ b/Docs/Layers/birdhide.md @@ -71,6 +71,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -148,6 +150,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -188,6 +192,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/cafe_pub.md b/Docs/Layers/cafe_pub.md index ad4bfe782a..7ac827f9c9 100644 --- a/Docs/Layers/cafe_pub.md +++ b/Docs/Layers/cafe_pub.md @@ -83,6 +83,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -375,6 +377,8 @@ This tagrendering is only visible in the popup if the following condition is met +Shows the reviews module (including the possibility to leave a review) + This tagrendering has no question and is thus read-only @@ -395,6 +399,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -435,6 +441,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/car_rental.md b/Docs/Layers/car_rental.md index f96fcd0528..a9f8327891 100644 --- a/Docs/Layers/car_rental.md +++ b/Docs/Layers/car_rental.md @@ -61,6 +61,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -174,6 +176,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -194,6 +198,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/caravansites.md b/Docs/Layers/caravansites.md index 8aeea4d721..19ae7dd4a2 100644 --- a/Docs/Layers/caravansites.md +++ b/Docs/Layers/caravansites.md @@ -81,6 +81,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -264,6 +266,8 @@ This is rendered with `More details about this place: {description}` +Show the images block at this location + This tagrendering has no question and is thus read-only @@ -274,6 +278,8 @@ This tagrendering has no question and is thus read-only +Shows the reviews module (including the possibility to leave a review) + This tagrendering has no question and is thus read-only @@ -284,6 +290,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -294,6 +302,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/charging_station.md b/Docs/Layers/charging_station.md index 6cfadb0287..9e6df97e4e 100644 --- a/Docs/Layers/charging_station.md +++ b/Docs/Layers/charging_station.md @@ -149,6 +149,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -2032,6 +2034,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -2072,6 +2076,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/charging_station_ebikes.md b/Docs/Layers/charging_station_ebikes.md index f45fc477f0..09f1d43969 100644 --- a/Docs/Layers/charging_station_ebikes.md +++ b/Docs/Layers/charging_station_ebikes.md @@ -148,6 +148,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -2031,6 +2033,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -2071,6 +2075,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/climbing.md b/Docs/Layers/climbing.md index 6bb1c002b2..ba1e41bf86 100644 --- a/Docs/Layers/climbing.md +++ b/Docs/Layers/climbing.md @@ -246,6 +246,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -256,6 +258,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/climbing_area.md b/Docs/Layers/climbing_area.md index 27fb1c0f47..be275a6fa5 100644 --- a/Docs/Layers/climbing_area.md +++ b/Docs/Layers/climbing_area.md @@ -79,6 +79,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -253,6 +255,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/climbing_club.md b/Docs/Layers/climbing_club.md index 6dce71b1bf..7b873032c9 100644 --- a/Docs/Layers/climbing_club.md +++ b/Docs/Layers/climbing_club.md @@ -171,6 +171,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -181,6 +183,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/climbing_gym.md b/Docs/Layers/climbing_gym.md index ee22afa602..317097a85c 100644 --- a/Docs/Layers/climbing_gym.md +++ b/Docs/Layers/climbing_gym.md @@ -85,6 +85,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -378,6 +380,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -388,6 +392,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/climbing_opportunity.md b/Docs/Layers/climbing_opportunity.md index c2bee4309b..91ba29f024 100644 --- a/Docs/Layers/climbing_opportunity.md +++ b/Docs/Layers/climbing_opportunity.md @@ -99,6 +99,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -109,6 +111,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/climbing_route.md b/Docs/Layers/climbing_route.md index ea27ae2bb2..dc0c909b9a 100644 --- a/Docs/Layers/climbing_route.md +++ b/Docs/Layers/climbing_route.md @@ -75,6 +75,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -189,6 +191,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -199,6 +203,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/clock.md b/Docs/Layers/clock.md index f898bef49d..cd0947b292 100644 --- a/Docs/Layers/clock.md +++ b/Docs/Layers/clock.md @@ -77,6 +77,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -243,6 +245,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -283,6 +287,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/crab_address.md b/Docs/Layers/crab_address.md index d1429e744a..1c00c9e585 100644 --- a/Docs/Layers/crab_address.md +++ b/Docs/Layers/crab_address.md @@ -67,6 +67,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -77,6 +79,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/crossings.md b/Docs/Layers/crossings.md index 80eb9a2d0d..ea21c52ae2 100644 --- a/Docs/Layers/crossings.md +++ b/Docs/Layers/crossings.md @@ -84,6 +84,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -329,6 +331,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -339,6 +343,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/cultural_places_without_etymology.md b/Docs/Layers/cultural_places_without_etymology.md index 5ec96a4565..9ad82ca226 100644 --- a/Docs/Layers/cultural_places_without_etymology.md +++ b/Docs/Layers/cultural_places_without_etymology.md @@ -125,6 +125,8 @@ This is rendered with `Named after {name:etymology}` +Show the images block at this location + This tagrendering has no question and is thus read-only @@ -177,6 +179,8 @@ This tagrendering is only visible in the popup if the following condition is met +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/cycleways_and_roads.md b/Docs/Layers/cycleways_and_roads.md index 8178273de9..64545d76a1 100644 --- a/Docs/Layers/cycleways_and_roads.md +++ b/Docs/Layers/cycleways_and_roads.md @@ -437,6 +437,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -457,6 +459,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/defibrillator.md b/Docs/Layers/defibrillator.md index d87b6e4e5e..94da205aa6 100644 --- a/Docs/Layers/defibrillator.md +++ b/Docs/Layers/defibrillator.md @@ -84,6 +84,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -356,6 +358,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -396,6 +400,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/dentist.md b/Docs/Layers/dentist.md index 20281d7ff8..844e473b67 100644 --- a/Docs/Layers/dentist.md +++ b/Docs/Layers/dentist.md @@ -73,6 +73,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -181,6 +183,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -221,6 +225,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/direction.md b/Docs/Layers/direction.md index 90d44676f4..c36de49d5d 100644 --- a/Docs/Layers/direction.md +++ b/Docs/Layers/direction.md @@ -69,6 +69,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -79,6 +81,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/doctors.md b/Docs/Layers/doctors.md index 0ea9db7285..f2c9573844 100644 --- a/Docs/Layers/doctors.md +++ b/Docs/Layers/doctors.md @@ -75,6 +75,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -212,6 +214,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -252,6 +256,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/dogfoodb.md b/Docs/Layers/dogfoodb.md index 15ef27c2a7..bd170361fa 100644 --- a/Docs/Layers/dogfoodb.md +++ b/Docs/Layers/dogfoodb.md @@ -94,6 +94,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -616,6 +618,8 @@ This tagrendering is only visible in the popup if the following condition is met +Shows the reviews module (including the possibility to leave a review) + This tagrendering has no question and is thus read-only @@ -636,6 +640,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -676,6 +682,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/dogpark.md b/Docs/Layers/dogpark.md index 8249ae3a03..507a07b0e3 100644 --- a/Docs/Layers/dogpark.md +++ b/Docs/Layers/dogpark.md @@ -127,6 +127,8 @@ This tagrendering has no question and is thus read-only +Shows the reviews module (including the possibility to leave a review) + This tagrendering has no question and is thus read-only @@ -137,6 +139,8 @@ This tagrendering has no question and is thus read-only +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -157,6 +161,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -167,6 +173,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/dogshop.md b/Docs/Layers/dogshop.md index ea510389e2..f331cfe375 100644 --- a/Docs/Layers/dogshop.md +++ b/Docs/Layers/dogshop.md @@ -79,6 +79,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -503,6 +505,8 @@ This tagrendering is only visible in the popup if the following condition is met +Show the images block at this location + This tagrendering has no question and is thus read-only @@ -513,6 +517,8 @@ This tagrendering has no question and is thus read-only +Shows the reviews module (including the possibility to leave a review) + This tagrendering has no question and is thus read-only @@ -523,6 +529,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -563,6 +571,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/drinking_water.md b/Docs/Layers/drinking_water.md index d73207c5bd..493ec0d0b6 100644 --- a/Docs/Layers/drinking_water.md +++ b/Docs/Layers/drinking_water.md @@ -77,6 +77,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -146,6 +148,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -186,6 +190,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/dumpstations.md b/Docs/Layers/dumpstations.md index 6b1e3cedd7..b871de7a44 100644 --- a/Docs/Layers/dumpstations.md +++ b/Docs/Layers/dumpstations.md @@ -77,6 +77,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -210,6 +212,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -220,6 +224,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/education_institutions_without_etymology.md b/Docs/Layers/education_institutions_without_etymology.md index 27861f66aa..7eb11c5421 100644 --- a/Docs/Layers/education_institutions_without_etymology.md +++ b/Docs/Layers/education_institutions_without_etymology.md @@ -125,6 +125,8 @@ This is rendered with `Named after {name:etymology}` +Show the images block at this location + This tagrendering has no question and is thus read-only @@ -177,6 +179,8 @@ This tagrendering is only visible in the popup if the following condition is met +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/elevator.md b/Docs/Layers/elevator.md index 2442dd21ae..03471b952b 100644 --- a/Docs/Layers/elevator.md +++ b/Docs/Layers/elevator.md @@ -76,6 +76,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -172,6 +174,8 @@ This is rendered with `This elevator has a depth of {canonical(elevator:depth)} +An accessibility feature: induction loops are for hard-hearing persons which have an FM-receiver. + The question is *Does this place have an audio induction loop for people with reduced hearing?* @@ -208,6 +212,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -218,6 +224,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/entrance.md b/Docs/Layers/entrance.md index 7dbecc8f05..1212c14142 100644 --- a/Docs/Layers/entrance.md +++ b/Docs/Layers/entrance.md @@ -79,6 +79,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -226,6 +228,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -236,6 +240,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/etymology.md b/Docs/Layers/etymology.md index 2a4abba9ad..fca2a72873 100644 --- a/Docs/Layers/etymology.md +++ b/Docs/Layers/etymology.md @@ -125,6 +125,8 @@ This is rendered with `Named after {name:etymology}` +Show the images block at this location + This tagrendering has no question and is thus read-only @@ -177,6 +179,8 @@ This tagrendering is only visible in the popup if the following condition is met +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/extinguisher.md b/Docs/Layers/extinguisher.md index 4cb4dac855..b9a969da93 100644 --- a/Docs/Layers/extinguisher.md +++ b/Docs/Layers/extinguisher.md @@ -89,6 +89,8 @@ This is rendered with `Location: {location}` +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -109,6 +111,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -119,6 +123,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/facadegardens.md b/Docs/Layers/facadegardens.md index ee114c251b..12321575a0 100644 --- a/Docs/Layers/facadegardens.md +++ b/Docs/Layers/facadegardens.md @@ -75,6 +75,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -204,6 +206,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -244,6 +248,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/fietsstraat.md b/Docs/Layers/fietsstraat.md index b92032b5dc..67e2cd72de 100644 --- a/Docs/Layers/fietsstraat.md +++ b/Docs/Layers/fietsstraat.md @@ -69,6 +69,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -89,6 +91,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -99,6 +103,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/fire_station.md b/Docs/Layers/fire_station.md index 7002ba80a6..506cc307f8 100644 --- a/Docs/Layers/fire_station.md +++ b/Docs/Layers/fire_station.md @@ -156,6 +156,8 @@ This is rendered with `The operator is a(n) {operator:type} entity.` +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -176,6 +178,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -186,6 +190,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/fitness_centre.md b/Docs/Layers/fitness_centre.md index 77a5906d1d..8f59ede08b 100644 --- a/Docs/Layers/fitness_centre.md +++ b/Docs/Layers/fitness_centre.md @@ -94,6 +94,8 @@ This is rendered with `This fitness centre is called {name}` +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -221,6 +223,8 @@ This is rendered with `Located on the {level}th floor` +Shows the reviews module (including the possibility to leave a review) + This tagrendering has no question and is thus read-only @@ -241,6 +245,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -251,6 +257,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/fitness_station.md b/Docs/Layers/fitness_station.md index c1306ab897..76a1c908a5 100644 --- a/Docs/Layers/fitness_station.md +++ b/Docs/Layers/fitness_station.md @@ -72,6 +72,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -178,6 +180,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -188,6 +192,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/food.md b/Docs/Layers/food.md index 0d1b8d612c..b0ca3de318 100644 --- a/Docs/Layers/food.md +++ b/Docs/Layers/food.md @@ -97,6 +97,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -619,6 +621,8 @@ This tagrendering is only visible in the popup if the following condition is met +Shows the reviews module (including the possibility to leave a review) + This tagrendering has no question and is thus read-only @@ -639,6 +643,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -679,6 +685,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/friture.md b/Docs/Layers/friture.md index 4660cad0e9..45d64486f5 100644 --- a/Docs/Layers/friture.md +++ b/Docs/Layers/friture.md @@ -94,6 +94,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -616,6 +618,8 @@ This tagrendering is only visible in the popup if the following condition is met +Shows the reviews module (including the possibility to leave a review) + This tagrendering has no question and is thus read-only @@ -636,6 +640,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -676,6 +682,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/ghost_bike.md b/Docs/Layers/ghost_bike.md index deb3e0efc8..7f0381e5ab 100644 --- a/Docs/Layers/ghost_bike.md +++ b/Docs/Layers/ghost_bike.md @@ -82,6 +82,8 @@ This tagrendering has no question and is thus read-only +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -163,6 +165,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -203,6 +207,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/governments.md b/Docs/Layers/governments.md index 102b8f8fa8..1c00194cd7 100644 --- a/Docs/Layers/governments.md +++ b/Docs/Layers/governments.md @@ -72,6 +72,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -166,6 +168,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -176,6 +180,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/hackerspace.md b/Docs/Layers/hackerspace.md index a39bb95371..0c0d2af363 100644 --- a/Docs/Layers/hackerspace.md +++ b/Docs/Layers/hackerspace.md @@ -62,6 +62,7 @@ attribute | type | values which are supported by this layer ----------- | ------ | ------------------------------------------ [](https://taginfo.openstreetmap.org/keys/hackerspace#values) [hackerspace](https://wiki.openstreetmap.org/wiki/Key:hackerspace) | Multiple choice | [makerspace](https://wiki.openstreetmap.org/wiki/Tag:hackerspace%3Dmakerspace) [](https://wiki.openstreetmap.org/wiki/Tag:hackerspace%3D) [](https://taginfo.openstreetmap.org/keys/name#values) [name](https://wiki.openstreetmap.org/wiki/Key:name) | [string](../SpecialInputElements.md#string) | +[](https://taginfo.openstreetmap.org/keys/level#values) [level](https://wiki.openstreetmap.org/wiki/Key:level) | [float](../SpecialInputElements.md#float) | [0](https://wiki.openstreetmap.org/wiki/Tag:level%3D0) [1](https://wiki.openstreetmap.org/wiki/Tag:level%3D1) [-1](https://wiki.openstreetmap.org/wiki/Tag:level%3D-1) [](https://taginfo.openstreetmap.org/keys/website#values) [website](https://wiki.openstreetmap.org/wiki/Key:website) | [url](../SpecialInputElements.md#url) | [](https://taginfo.openstreetmap.org/keys/email#values) [email](https://wiki.openstreetmap.org/wiki/Key:email) | [email](../SpecialInputElements.md#email) | [](https://taginfo.openstreetmap.org/keys/phone#values) [phone](https://wiki.openstreetmap.org/wiki/Key:phone) | [phone](../SpecialInputElements.md#phone) | @@ -80,6 +81,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -116,6 +119,31 @@ This is rendered with `This hackerspace is named {name}` +### level + + + +The question is *On what level is this feature located?* + +This rendering asks information about the property [level](https://wiki.openstreetmap.org/wiki/Key:level) + +This is rendered with `Located on the {level}th floor` + + + + + + - *Located underground* corresponds with `location=underground` + - This option cannot be chosen as answer + - *Located on the ground floor* corresponds with `level=0` + - *Located on the ground floor* corresponds with `` + - This option cannot be chosen as answer + - *Located on the first floor* corresponds with `level=1` + - *Located on the first basement level* corresponds with `level=-1` + + + + ### website @@ -243,16 +271,6 @@ The question is *Is a CNC drill available at this hackerspace?* -### reviews - - - -This tagrendering has no question and is thus read-only - - - - - ### wheelchair-access @@ -301,7 +319,43 @@ This is rendered with `This hackerspace was founded at {start_date}` -### leftover-questions +### questions + + + +Show the images block at this location + +This tagrendering has no question and is thus read-only + + + + + +### reviews + + + +Shows the reviews module (including the possibility to leave a review) + +This tagrendering has no question and is thus read-only + + + + + +### minimap + + + +Shows a small map with the feature. Added by default to every popup + +This tagrendering has no question and is thus read-only + + + + + +### move-button @@ -311,7 +365,17 @@ This tagrendering has no question and is thus read-only -### minimap +### delete-button + + + +This tagrendering has no question and is thus read-only + + + + + +### all-tags @@ -325,6 +389,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/health_and_social_places_without_etymology.md b/Docs/Layers/health_and_social_places_without_etymology.md index 1768354427..2e00458f6b 100644 --- a/Docs/Layers/health_and_social_places_without_etymology.md +++ b/Docs/Layers/health_and_social_places_without_etymology.md @@ -125,6 +125,8 @@ This is rendered with `Named after {name:etymology}` +Show the images block at this location + This tagrendering has no question and is thus read-only @@ -177,6 +179,8 @@ This tagrendering is only visible in the popup if the following condition is met +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/hospital.md b/Docs/Layers/hospital.md index 4ce236216e..e637ee3cb7 100644 --- a/Docs/Layers/hospital.md +++ b/Docs/Layers/hospital.md @@ -174,6 +174,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -184,6 +186,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/hotel.md b/Docs/Layers/hotel.md index 0f3d68e570..90f8d91518 100644 --- a/Docs/Layers/hotel.md +++ b/Docs/Layers/hotel.md @@ -77,6 +77,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -87,6 +89,8 @@ This tagrendering has no question and is thus read-only +Shows the reviews module (including the possibility to leave a review) + This tagrendering has no question and is thus read-only @@ -259,6 +263,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -279,6 +285,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/hydrant.md b/Docs/Layers/hydrant.md index a7e078caa8..8e1ba71767 100644 --- a/Docs/Layers/hydrant.md +++ b/Docs/Layers/hydrant.md @@ -197,6 +197,8 @@ This is rendered with `Coupling diameters: {couplings:diameters}` +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -217,6 +219,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -227,6 +231,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/indoors.md b/Docs/Layers/indoors.md index 9e58583974..5c0627bce7 100644 --- a/Docs/Layers/indoors.md +++ b/Docs/Layers/indoors.md @@ -72,6 +72,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -124,6 +126,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -134,6 +138,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/information_board.md b/Docs/Layers/information_board.md index b4dad3c737..48a8234d38 100644 --- a/Docs/Layers/information_board.md +++ b/Docs/Layers/information_board.md @@ -58,6 +58,8 @@ Elements must have the all of following tags to be shown on this layer: +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -78,6 +80,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -118,6 +122,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/kerbs.md b/Docs/Layers/kerbs.md index 24061f66e7..e033cbc4ea 100644 --- a/Docs/Layers/kerbs.md +++ b/Docs/Layers/kerbs.md @@ -76,6 +76,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -158,6 +160,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -168,6 +172,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/kindergarten_childcare.md b/Docs/Layers/kindergarten_childcare.md index 22ee195cd7..7d68cf339e 100644 --- a/Docs/Layers/kindergarten_childcare.md +++ b/Docs/Layers/kindergarten_childcare.md @@ -205,6 +205,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -245,6 +247,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/lit_streets.md b/Docs/Layers/lit_streets.md index ce4cee79e6..758e9dfb23 100644 --- a/Docs/Layers/lit_streets.md +++ b/Docs/Layers/lit_streets.md @@ -70,6 +70,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -109,6 +111,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -129,6 +133,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/map.md b/Docs/Layers/map.md index 47bbffc597..e9ddb20755 100644 --- a/Docs/Layers/map.md +++ b/Docs/Layers/map.md @@ -71,6 +71,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -136,6 +138,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -176,6 +180,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/maproulette.md b/Docs/Layers/maproulette.md index 3075277ab6..57947ca537 100644 --- a/Docs/Layers/maproulette.md +++ b/Docs/Layers/maproulette.md @@ -120,6 +120,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -130,6 +132,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/maproulette_challenge.md b/Docs/Layers/maproulette_challenge.md index 64589618fd..01474a1cf5 100644 --- a/Docs/Layers/maproulette_challenge.md +++ b/Docs/Layers/maproulette_challenge.md @@ -113,6 +113,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -123,6 +125,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/maxspeed.md b/Docs/Layers/maxspeed.md index 3f8be9cb7b..2d1cf7514f 100644 --- a/Docs/Layers/maxspeed.md +++ b/Docs/Layers/maxspeed.md @@ -103,6 +103,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -123,6 +125,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/medical-shops.md b/Docs/Layers/medical-shops.md index e3b26dbcfb..432a345e3b 100644 --- a/Docs/Layers/medical-shops.md +++ b/Docs/Layers/medical-shops.md @@ -79,6 +79,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -503,6 +505,8 @@ This tagrendering is only visible in the popup if the following condition is met +Show the images block at this location + This tagrendering has no question and is thus read-only @@ -513,6 +517,8 @@ This tagrendering has no question and is thus read-only +Shows the reviews module (including the possibility to leave a review) + This tagrendering has no question and is thus read-only @@ -523,6 +529,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -563,6 +571,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/nature_reserve.md b/Docs/Layers/nature_reserve.md index 96ad7decc1..79177dde32 100644 --- a/Docs/Layers/nature_reserve.md +++ b/Docs/Layers/nature_reserve.md @@ -79,6 +79,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -273,6 +275,8 @@ This tagrendering has no question and is thus read-only +Shows a wikipedia box with the corresponding wikipedia article; the wikidata-item link can be changed by a contributor + The question is *What is the corresponding Wikidata entity?* This rendering asks information about the property [wikidata](https://wiki.openstreetmap.org/wiki/Key:wikidata) @@ -305,6 +309,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -315,6 +321,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/note.md b/Docs/Layers/note.md index bc97c5d2aa..78306662b7 100644 --- a/Docs/Layers/note.md +++ b/Docs/Layers/note.md @@ -131,6 +131,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -141,6 +143,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/observation_tower.md b/Docs/Layers/observation_tower.md index b99b86be4f..e9b9dbc9d8 100644 --- a/Docs/Layers/observation_tower.md +++ b/Docs/Layers/observation_tower.md @@ -78,6 +78,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -266,6 +268,8 @@ This tagrendering is only visible in the popup if the following condition is met +Shows a wikipedia box with the corresponding wikipedia article; the wikidata-item link can be changed by a contributor + The question is *What is the corresponding Wikidata entity?* This rendering asks information about the property [wikidata](https://wiki.openstreetmap.org/wiki/Key:wikidata) @@ -298,6 +302,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -318,6 +324,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/osm_community_index.md b/Docs/Layers/osm_community_index.md index c866f7946c..069757cdda 100644 --- a/Docs/Layers/osm_community_index.md +++ b/Docs/Layers/osm_community_index.md @@ -97,6 +97,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -107,6 +109,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/parcel_lockers.md b/Docs/Layers/parcel_lockers.md index 3aabb5f529..e60e917393 100644 --- a/Docs/Layers/parcel_lockers.md +++ b/Docs/Layers/parcel_lockers.md @@ -74,6 +74,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -199,6 +201,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -239,6 +243,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/parking.md b/Docs/Layers/parking.md index 6c8a6b60e4..1bc19b716b 100644 --- a/Docs/Layers/parking.md +++ b/Docs/Layers/parking.md @@ -74,6 +74,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -180,6 +182,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -220,6 +224,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/parking_spaces.md b/Docs/Layers/parking_spaces.md index 88bf5a2342..c4832e2836 100644 --- a/Docs/Layers/parking_spaces.md +++ b/Docs/Layers/parking_spaces.md @@ -71,6 +71,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -136,6 +138,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -146,6 +150,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/parking_ticket_machine.md b/Docs/Layers/parking_ticket_machine.md index bd6fee7cda..d0ffe20abd 100644 --- a/Docs/Layers/parking_ticket_machine.md +++ b/Docs/Layers/parking_ticket_machine.md @@ -72,6 +72,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -186,6 +188,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -196,6 +200,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/parks_and_forests_without_etymology.md b/Docs/Layers/parks_and_forests_without_etymology.md index f00949229b..19bc4731c5 100644 --- a/Docs/Layers/parks_and_forests_without_etymology.md +++ b/Docs/Layers/parks_and_forests_without_etymology.md @@ -125,6 +125,8 @@ This is rendered with `Named after {name:etymology}` +Show the images block at this location + This tagrendering has no question and is thus read-only @@ -177,6 +179,8 @@ This tagrendering is only visible in the popup if the following condition is met +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/pharmacy.md b/Docs/Layers/pharmacy.md index 6a6dfa51a6..43fd6515c2 100644 --- a/Docs/Layers/pharmacy.md +++ b/Docs/Layers/pharmacy.md @@ -76,6 +76,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -201,6 +203,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -241,6 +245,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/physiotherapist.md b/Docs/Layers/physiotherapist.md index 83cf6b96a4..7615ae6f27 100644 --- a/Docs/Layers/physiotherapist.md +++ b/Docs/Layers/physiotherapist.md @@ -73,6 +73,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -188,6 +190,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -228,6 +232,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/picnic_table.md b/Docs/Layers/picnic_table.md index 65dbf2af94..bf35d98fcb 100644 --- a/Docs/Layers/picnic_table.md +++ b/Docs/Layers/picnic_table.md @@ -71,6 +71,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -137,6 +139,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -177,6 +181,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/play_forest.md b/Docs/Layers/play_forest.md index e99655d856..4cc73cfa7f 100644 --- a/Docs/Layers/play_forest.md +++ b/Docs/Layers/play_forest.md @@ -60,6 +60,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -135,6 +137,8 @@ This is rendered with `De bevoegde dienst kan getelefoneerd worden via {phone}` +Show the images block at this location + This tagrendering has no question and is thus read-only @@ -155,6 +159,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -165,6 +171,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/playground.md b/Docs/Layers/playground.md index 3b331fe61f..fc2af955b2 100644 --- a/Docs/Layers/playground.md +++ b/Docs/Layers/playground.md @@ -79,6 +79,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -287,6 +289,8 @@ This is rendered with `{opening_hours_table(opening_hours)}` +Show the images block at this location + This tagrendering has no question and is thus read-only @@ -307,6 +311,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -337,6 +343,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/postboxes.md b/Docs/Layers/postboxes.md index d1efe1b02a..e0a7e3152a 100644 --- a/Docs/Layers/postboxes.md +++ b/Docs/Layers/postboxes.md @@ -58,6 +58,8 @@ Elements must have the all of following tags to be shown on this layer: +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -108,6 +110,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/postoffices.md b/Docs/Layers/postoffices.md index 9fb8a6a308..4be0a90fcc 100644 --- a/Docs/Layers/postoffices.md +++ b/Docs/Layers/postoffices.md @@ -76,6 +76,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -266,6 +268,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/public_bookcase.md b/Docs/Layers/public_bookcase.md index b65ad6d649..3b0acb360c 100644 --- a/Docs/Layers/public_bookcase.md +++ b/Docs/Layers/public_bookcase.md @@ -78,6 +78,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -270,6 +272,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -310,6 +314,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/railway_platforms.md b/Docs/Layers/railway_platforms.md index 8fc5bd4727..755676529d 100644 --- a/Docs/Layers/railway_platforms.md +++ b/Docs/Layers/railway_platforms.md @@ -107,6 +107,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -117,6 +119,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/rainbow_crossing_high_zoom.md b/Docs/Layers/rainbow_crossing_high_zoom.md index cd2a58985b..0d29e9ed6e 100644 --- a/Docs/Layers/rainbow_crossing_high_zoom.md +++ b/Docs/Layers/rainbow_crossing_high_zoom.md @@ -59,6 +59,8 @@ Elements must have the all of following tags to be shown on this layer: +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -99,6 +101,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -109,6 +113,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/rainbow_crossings.md b/Docs/Layers/rainbow_crossings.md index dbbfb6e4ba..070083e35f 100644 --- a/Docs/Layers/rainbow_crossings.md +++ b/Docs/Layers/rainbow_crossings.md @@ -59,6 +59,8 @@ Elements must have the all of following tags to be shown on this layer: +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -99,6 +101,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -109,6 +113,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/reception_desk.md b/Docs/Layers/reception_desk.md index a847ab9057..718b061ddd 100644 --- a/Docs/Layers/reception_desk.md +++ b/Docs/Layers/reception_desk.md @@ -71,6 +71,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -120,6 +122,8 @@ This is rendered with `The height of the desk is {canonical(desk:height)}{name}` +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -254,6 +256,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -264,6 +268,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/tree_node.md b/Docs/Layers/tree_node.md index 77b05c9f18..45534f5d99 100644 --- a/Docs/Layers/tree_node.md +++ b/Docs/Layers/tree_node.md @@ -78,6 +78,8 @@ attribute | type | values which are supported by this layer +This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata` + This tagrendering has no question and is thus read-only @@ -293,6 +295,8 @@ This tagrendering has no question and is thus read-only +Shows a small map with the feature. Added by default to every popup + This tagrendering has no question and is thus read-only @@ -333,6 +337,8 @@ This tagrendering has no question and is thus read-only +Gives some metainfo about the last edit and who did edit it - rendering only + This tagrendering has no question and is thus read-only diff --git a/Docs/Layers/veterinary.md b/Docs/Layers/veterinary.md index ddb4330ff8..29259b9014 100644 --- a/Docs/Layers/veterinary.md +++ b/Docs/Layers/veterinary.md @@ -92,6 +92,8 @@ This is rendered with `questions[key], "questions.json:" + key ) - delete config.description delete config["#"] validator.convertStrict( config, From 8eb2c68f79b7512d96d34ccadb705decafa4a0a8 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 20 Apr 2023 01:52:23 +0200 Subject: [PATCH 048/257] Refactoring: fix rendering of new roads, generated by a split --- Logic/Actors/ChangeToElementsActor.ts | 5 +- .../Actors/SaveFeatureSourceToLocalStorage.ts | 3 +- .../FeatureSource/Actors/TileLocalStorage.ts | 11 +- .../Sources/ChangeGeometryApplicator.ts | 18 +- .../Sources/FeatureSourceMerger.ts | 5 +- Logic/FeatureSource/Sources/LayoutSource.ts | 30 ++-- .../NewGeometryFromChangesFeatureSource.ts | 10 +- .../FeatureSource/Sources/OsmFeatureSource.ts | 3 +- .../Sources/SnappingFeatureSource.ts | 9 +- .../LocalStorageFeatureSource.ts | 3 +- Logic/GeoOperations.ts | 9 + Logic/Osm/Actions/SplitAction.ts | 9 +- Logic/Osm/Changes.ts | 4 - Logic/SimpleMetaTagger.ts | 2 +- Logic/Web/IdbLocalStorage.ts | 5 +- Models/Constants.ts | 1 + Models/ThemeConfig/Conversion/PrepareLayer.ts | 23 +-- Models/ThemeViewState.ts | 123 +++++++------ UI/BigComponents/ActionButtons.ts | 9 - UI/BigComponents/WaySplitMap.svelte | 104 +++++++++++ UI/Map/MapLibreAdaptor.ts | 11 +- UI/Map/MaplibreMap.svelte | 2 +- UI/Map/ShowDataLayer.ts | 78 ++++++--- UI/Popup/AddNewPoint/AddNewPoint.svelte | 14 +- UI/Popup/CreateNewNote.svelte | 1 + UI/Popup/LoginButton.ts | 25 +-- UI/Popup/SplitRoadWizard.ts | 163 +++++------------- UI/SpecialVisualization.ts | 30 ++-- UI/SpecialVisualizations.ts | 16 +- assets/layers/split_road/split_road.json | 21 +++ assets/themes/cyclestreets/cyclestreets.json | 4 +- css/index-tailwind-output.css | 12 +- test.html | 2 +- test.ts | 11 +- 34 files changed, 443 insertions(+), 333 deletions(-) create mode 100644 UI/BigComponents/WaySplitMap.svelte create mode 100644 assets/layers/split_road/split_road.json diff --git a/Logic/Actors/ChangeToElementsActor.ts b/Logic/Actors/ChangeToElementsActor.ts index 3354dbfa69..3bb0f50ad4 100644 --- a/Logic/Actors/ChangeToElementsActor.ts +++ b/Logic/Actors/ChangeToElementsActor.ts @@ -1,6 +1,9 @@ import { Changes } from "../Osm/Changes" -import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"; +import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" +/** + * Applies tag changes onto the featureStore + */ export default class ChangeToElementsActor { constructor(changes: Changes, allElements: FeaturePropertiesStore) { changes.pendingChanges.addCallbackAndRun((changes) => { diff --git a/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts b/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts index dbb4ae8559..47e777d211 100644 --- a/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts +++ b/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts @@ -55,12 +55,13 @@ class SingleTileSaver { */ export default class SaveFeatureSourceToLocalStorage { constructor( + backend: string, layername: string, zoomlevel: number, features: FeatureSource, featureProperties: FeaturePropertiesStore ) { - const storage = TileLocalStorage.construct(layername) + const storage = TileLocalStorage.construct(backend, layername) const singleTileSavers: Map = new Map() features.features.addCallbackAndRunD((features) => { const sliced = GeoOperations.slice(zoomlevel, features) diff --git a/Logic/FeatureSource/Actors/TileLocalStorage.ts b/Logic/FeatureSource/Actors/TileLocalStorage.ts index 9ee40c6034..dc92acf855 100644 --- a/Logic/FeatureSource/Actors/TileLocalStorage.ts +++ b/Logic/FeatureSource/Actors/TileLocalStorage.ts @@ -17,14 +17,15 @@ export default class TileLocalStorage { this._layername = layername } - public static construct(layername: string): TileLocalStorage { - const cached = TileLocalStorage.perLayer[layername] + public static construct(backend: string, layername: string): TileLocalStorage { + const key = backend + "_" + layername + const cached = TileLocalStorage.perLayer[key] if (cached) { return cached } - const tls = new TileLocalStorage(layername) - TileLocalStorage.perLayer[layername] = tls + const tls = new TileLocalStorage(key) + TileLocalStorage.perLayer[key] = tls return tls } @@ -46,7 +47,7 @@ export default class TileLocalStorage { return src } - private async SetIdb(tileIndex: number, data): Promise { + private async SetIdb(tileIndex: number, data: any): Promise { try { await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data) } catch (e) { diff --git a/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts b/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts index fb92d69c67..b0675c1bc5 100644 --- a/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts +++ b/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts @@ -3,22 +3,18 @@ */ import { Changes } from "../../Osm/Changes" import { UIEventSource } from "../../UIEventSource" -import { FeatureSourceForLayer, IndexedFeatureSource } from "../FeatureSource" -import FilteredLayer from "../../../Models/FilteredLayer" +import { FeatureSource, IndexedFeatureSource } from "../FeatureSource" import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription" import { Feature } from "geojson" -export default class ChangeGeometryApplicator implements FeatureSourceForLayer { - public readonly features: UIEventSource = - new UIEventSource([]) - public readonly layer: FilteredLayer +export default class ChangeGeometryApplicator implements FeatureSource { + public readonly features: UIEventSource = new UIEventSource([]) private readonly source: IndexedFeatureSource private readonly changes: Changes - constructor(source: IndexedFeatureSource & FeatureSourceForLayer, changes: Changes) { + constructor(source: IndexedFeatureSource, changes: Changes) { this.source = source this.changes = changes - this.layer = source.layer this.features = new UIEventSource(undefined) @@ -30,10 +26,10 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer { private update() { const upstreamFeatures = this.source.features.data - const upstreamIds = this.source.containedIds.data + const upstreamIds = this.source.featuresById.data const changesToApply = this.changes.allChanges.data?.filter( (ch) => - // Does upsteram have this element? If not, we skip + // Does upstream have this element? If not, we skip upstreamIds.has(ch.type + "/" + ch.id) && // Are any (geometry) changes defined? ch.changes !== undefined && @@ -61,7 +57,7 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer { for (const feature of upstreamFeatures) { const changesForFeature = changesPerId.get(feature.properties.id) if (changesForFeature === undefined) { - // No changes for this element + // No changes for this element - simply pass it along to downstream newFeatures.push(feature) continue } diff --git a/Logic/FeatureSource/Sources/FeatureSourceMerger.ts b/Logic/FeatureSource/Sources/FeatureSourceMerger.ts index cd5bda68a5..52121ada2c 100644 --- a/Logic/FeatureSource/Sources/FeatureSourceMerger.ts +++ b/Logic/FeatureSource/Sources/FeatureSourceMerger.ts @@ -1,5 +1,5 @@ import { Store, UIEventSource } from "../../UIEventSource" -import { FeatureSource , IndexedFeatureSource } from "../FeatureSource" +import { FeatureSource, IndexedFeatureSource } from "../FeatureSource" import { Feature } from "geojson" import { Utils } from "../../../Utils" @@ -19,6 +19,7 @@ export default class FeatureSourceMerger implements IndexedFeatureSource { this._featuresById = new UIEventSource>(undefined) this.featuresById = this._featuresById const self = this + sources = Utils.NoNull(sources) for (let source of sources) { source.features.addCallback(() => { self.addData(sources.map((s) => s.features.data)) @@ -28,7 +29,7 @@ export default class FeatureSourceMerger implements IndexedFeatureSource { this._sources = sources } - protected addSource(source: FeatureSource) { + public addSource(source: FeatureSource) { this._sources.push(source) source.features.addCallbackAndRun(() => { this.addData(this._sources.map((s) => s.features.data)) diff --git a/Logic/FeatureSource/Sources/LayoutSource.ts b/Logic/FeatureSource/Sources/LayoutSource.ts index 53626bc14a..e142a489a1 100644 --- a/Logic/FeatureSource/Sources/LayoutSource.ts +++ b/Logic/FeatureSource/Sources/LayoutSource.ts @@ -4,13 +4,14 @@ import { FeatureSource } from "../FeatureSource" import { Or } from "../../Tags/Or" import FeatureSwitchState from "../../State/FeatureSwitchState" import OverpassFeatureSource from "./OverpassFeatureSource" -import { ImmutableStore, Store } from "../../UIEventSource" +import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" import OsmFeatureSource from "./OsmFeatureSource" import FeatureSourceMerger from "./FeatureSourceMerger" import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource" import { BBox } from "../../BBox" import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeatureSource" import StaticFeatureSource from "./StaticFeatureSource" +import { OsmPreferences } from "../../Osm/OsmPreferences" /** * This source will fetch the needed data from various sources for the given layout. @@ -18,15 +19,14 @@ import StaticFeatureSource from "./StaticFeatureSource" * Note that special layers (with `source=null` will be ignored) */ export default class LayoutSource extends FeatureSourceMerger { + private readonly _isLoading: UIEventSource = new UIEventSource(false) /** * Indicates if a data source is loading something - * TODO fixme */ - public readonly isLoading: Store = new ImmutableStore(false) + public readonly isLoading: Store = this._isLoading constructor( layers: LayerConfig[], featureSwitches: FeatureSwitchState, - newAndChangedElements: FeatureSource, mapProperties: { bounds: Store; zoom: Store }, backend: string, isDisplayed: (id: string) => Store @@ -39,7 +39,7 @@ export default class LayoutSource extends FeatureSourceMerger { const osmLayers = layers.filter((layer) => layer.source.geojsonSource === undefined) const fromCache = osmLayers.map( (l) => - new LocalStorageFeatureSource(l.id, 15, mapProperties, { + new LocalStorageFeatureSource(backend, l.id, 15, mapProperties, { isActive: isDisplayed(l.id), }) ) @@ -56,7 +56,17 @@ export default class LayoutSource extends FeatureSourceMerger { ) const expiryInSeconds = Math.min(...(layers?.map((l) => l.maxAgeOfCache) ?? [])) - super(overpassSource, osmApiSource, newAndChangedElements, ...geojsonSources, ...fromCache) + + super(overpassSource, osmApiSource, ...geojsonSources, ...fromCache) + + const self = this + function setIsLoading() { + const loading = overpassSource?.runningQuery?.data && osmApiSource?.isRunning?.data + self._isLoading.setData(loading) + } + + overpassSource?.runningQuery?.addCallbackAndRun((_) => setIsLoading()) + osmApiSource?.isRunning?.addCallbackAndRun((_) => setIsLoading()) } private static setupGeojsonSource( @@ -83,9 +93,9 @@ export default class LayoutSource extends FeatureSourceMerger { zoom: Store, backend: string, featureSwitches: FeatureSwitchState - ): FeatureSource { + ): OsmFeatureSource | undefined { if (osmLayers.length == 0) { - return new StaticFeatureSource(new ImmutableStore([])) + return undefined } const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom)) const isActive = zoom.mapD((z) => { @@ -115,9 +125,9 @@ export default class LayoutSource extends FeatureSourceMerger { bounds: Store, zoom: Store, featureSwitches: FeatureSwitchState - ): FeatureSource { + ): OverpassFeatureSource | undefined { if (osmLayers.length == 0) { - return new StaticFeatureSource(new ImmutableStore([])) + return undefined } const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom)) const isActive = zoom.mapD((z) => { diff --git a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts index 639c375edb..44b6e13f58 100644 --- a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts +++ b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts @@ -1,13 +1,12 @@ import { Changes } from "../../Osm/Changes" import { OsmNode, OsmObject, OsmRelation, OsmWay } from "../../Osm/OsmObject" -import { FeatureSource } from "../FeatureSource" +import { IndexedFeatureSource, WritableFeatureSource } from "../FeatureSource" import { UIEventSource } from "../../UIEventSource" import { ChangeDescription } from "../../Osm/Actions/ChangeDescription" -import { ElementStorage } from "../../ElementStorage" import { OsmId, OsmTags } from "../../../Models/OsmFeature" import { Feature } from "geojson" -export class NewGeometryFromChangesFeatureSource implements FeatureSource { +export class NewGeometryFromChangesFeatureSource implements WritableFeatureSource { // This class name truly puts the 'Java' into 'Javascript' /** @@ -18,7 +17,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { */ public readonly features: UIEventSource = new UIEventSource([]) - constructor(changes: Changes, allElementStorage: ElementStorage, backendUrl: string) { + constructor(changes: Changes, allElementStorage: IndexedFeatureSource, backendUrl: string) { const seenChanges = new Set() const features = this.features.data const self = this @@ -53,7 +52,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { // In _most_ of the cases, this means that this _isn't_ a new object // However, when a point is snapped to an already existing point, we have to create a representation for this point! // For this, we introspect the change - if (allElementStorage.has(change.type + "/" + change.id)) { + if (allElementStorage.featuresById.data.has(change.type + "/" + change.id)) { // The current point already exists, we don't have to do anything here continue } @@ -65,7 +64,6 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { feat.tags[kv.k] = kv.v } const geojson = feat.asGeoJson() - allElementStorage.addOrGetElement(geojson) self.features.data.push(geojson) self.features.ping() }) diff --git a/Logic/FeatureSource/Sources/OsmFeatureSource.ts b/Logic/FeatureSource/Sources/OsmFeatureSource.ts index e55db7e942..91a819e2be 100644 --- a/Logic/FeatureSource/Sources/OsmFeatureSource.ts +++ b/Logic/FeatureSource/Sources/OsmFeatureSource.ts @@ -41,7 +41,6 @@ export default class OsmFeatureSource extends FeatureSourceMerger { this.isActive = options.isActive ?? new ImmutableStore(true) this._backend = options.backend ?? "https://www.openstreetmap.org" this._bounds.addCallbackAndRunD((bbox) => this.loadData(bbox)) - console.log("Allowed tags are:", this.allowedTags) } private async loadData(bbox: BBox) { @@ -108,7 +107,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger { } private async LoadTile(z, x, y): Promise { - console.log("OsmFeatureSource: loading ", z, x, y) + console.log("OsmFeatureSource: loading ", z, x, y, "from", this._backend) if (z >= 22) { throw "This is an absurd high zoom level" } diff --git a/Logic/FeatureSource/Sources/SnappingFeatureSource.ts b/Logic/FeatureSource/Sources/SnappingFeatureSource.ts index 43dab4d225..6cc85858f2 100644 --- a/Logic/FeatureSource/Sources/SnappingFeatureSource.ts +++ b/Logic/FeatureSource/Sources/SnappingFeatureSource.ts @@ -22,13 +22,18 @@ export interface SnappingOptions { * The resulting snap coordinates will be written into this UIEventSource */ snapLocation?: UIEventSource<{ lon: number; lat: number }> + + /** + * If the projected point is within `reusePointWithin`-meter of an already existing point + */ + reusePointWithin?: number } export default class SnappingFeatureSource implements FeatureSource { public readonly features: Store[]> - - private readonly _snappedTo: UIEventSource + /*Contains the id of the way it snapped to*/ public readonly snappedTo: Store + private readonly _snappedTo: UIEventSource constructor( snapTo: FeatureSource, diff --git a/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts b/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts index 972c48a8e7..d83db825e7 100644 --- a/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts @@ -7,6 +7,7 @@ import StaticFeatureSource from "../Sources/StaticFeatureSource" export default class LocalStorageFeatureSource extends DynamicTileSource { constructor( + backend: string, layername: string, zoomlevel: number, mapProperties: { @@ -17,7 +18,7 @@ export default class LocalStorageFeatureSource extends DynamicTileSource { isActive?: Store } ) { - const storage = TileLocalStorage.construct(layername) + const storage = TileLocalStorage.construct(backend, layername) super( zoomlevel, (tileIndex) => diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index c9af7c6e2a..741affcb9f 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -294,6 +294,10 @@ export class GeoOperations { * Mostly used as helper for 'nearestPoint' * @param way */ + public static forceLineString(way: Feature): Feature + public static forceLineString( + way: Feature + ): Feature public static forceLineString( way: Feature ): Feature { @@ -972,4 +976,9 @@ export class GeoOperations { }, } } + + static centerpointCoordinatesObj(geojson: Feature) { + const [lon, lat] = GeoOperations.centerpointCoordinates(geojson) + return { lon, lat } + } } diff --git a/Logic/Osm/Actions/SplitAction.ts b/Logic/Osm/Actions/SplitAction.ts index ba95dbe36a..6c8166220d 100644 --- a/Logic/Osm/Actions/SplitAction.ts +++ b/Logic/Osm/Actions/SplitAction.ts @@ -4,6 +4,7 @@ import { GeoOperations } from "../../GeoOperations" import OsmChangeAction from "./OsmChangeAction" import { ChangeDescription } from "./ChangeDescription" import RelationSplitHandler from "./RelationSplitHandler" +import { Feature, LineString } from "geojson" interface SplitInfo { originalIndex?: number // or negative for new elements @@ -14,9 +15,9 @@ interface SplitInfo { export default class SplitAction extends OsmChangeAction { private readonly wayId: string private readonly _splitPointsCoordinates: [number, number][] // lon, lat - private _meta: { theme: string; changeType: "split" } - private _toleranceInMeters: number - private _withNewCoordinates: (coordinates: [number, number][]) => void + private readonly _meta: { theme: string; changeType: "split" } + private readonly _toleranceInMeters: number + private readonly _withNewCoordinates: (coordinates: [number, number][]) => void /** * Create a changedescription for splitting a point. @@ -197,7 +198,7 @@ export default class SplitAction extends OsmChangeAction { * If another point is closer then ~5m, we reuse that point */ private CalculateSplitCoordinates(osmWay: OsmWay, toleranceInM = 5): SplitInfo[] { - const wayGeoJson = osmWay.asGeoJson() + const wayGeoJson = >osmWay.asGeoJson() // Should be [lon, lat][] const originalPoints: [number, number][] = osmWay.coordinates.map((c) => [c[1], c[0]]) const allPoints: { diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index f4cba09acc..b7580ad772 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -18,10 +18,6 @@ import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesSto * Needs an authenticator via OsmConnection */ export class Changes { - /** - * All the newly created features as featureSource + all the modified features - */ - public readonly features = new UIEventSource<{ feature: any; freshness: Date }[]>([]) public readonly pendingChanges: UIEventSource = LocalStorageSource.GetParsed("pending-changes", []) public readonly allChanges = new UIEventSource(undefined) diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index 246ba2f4e1..34e36d7cb2 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -213,7 +213,7 @@ class RewriteMetaInfoTags extends SimpleMetaTagger { move("changeset", "_last_edit:changeset") move("timestamp", "_last_edit:timestamp") move("version", "_version_number") - feature.properties._backend = "https://openstreetmap.org" + feature.properties._backend = feature.properties._backend ?? "https://openstreetmap.org" return movedSomething } } diff --git a/Logic/Web/IdbLocalStorage.ts b/Logic/Web/IdbLocalStorage.ts index 8abc2a9e5a..81ce2b6ebe 100644 --- a/Logic/Web/IdbLocalStorage.ts +++ b/Logic/Web/IdbLocalStorage.ts @@ -38,8 +38,9 @@ export class IdbLocalStorage { return src } - public static SetDirectly(key: string, value): Promise { - return idb.set(key, value) + public static SetDirectly(key: string, value: any): Promise { + const copy = Utils.Clone(value) + return idb.set(key, copy) } static GetDirectly(key: string): Promise { diff --git a/Models/Constants.ts b/Models/Constants.ts index 21bbe9223d..18b6cf429c 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -43,6 +43,7 @@ export default class Constants { public static readonly no_include = [ "conflation", "split_point", + "split_road", "current_view", "matchpoint", "import_candidate", diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index ad1ddae4d9..368b9f42e4 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -596,6 +596,7 @@ export class AddEditingElements extends DesugaringStep { id: "split-button", render: { "*": "{split_button()}" }, }) + delete json.allowSplit } if (json.allowMove && !ValidationUtils.hasSpecialVisualisation(json, "move_button")) { @@ -611,7 +612,16 @@ export class AddEditingElements extends DesugaringStep { }) } - if (json.deletion && !ValidationUtils.hasSpecialVisualisation(json, "all_tags")) { + if ( + json.source !== "special" && + json.source !== "special:library" && + json.tagRenderings && + !json.tagRenderings.some((tr) => tr["id"] === "last_edit") + ) { + json.tagRenderings.push(this._desugaring.tagRenderings.get("last_edit")) + } + + if (!ValidationUtils.hasSpecialVisualisation(json, "all_tags")) { const trc: TagRenderingConfigJson = { id: "all-tags", render: { "*": "{all_tags()}" }, @@ -623,16 +633,7 @@ export class AddEditingElements extends DesugaringStep { ], }, } - json.tagRenderings.push(trc) - } - - if ( - json.source !== "special" && - json.source !== "special:library" && - json.tagRenderings && - !json.tagRenderings.some((tr) => tr["id"] === "last_edit") - ) { - json.tagRenderings.push(this._desugaring.tagRenderings.get("last_edit")) + json.tagRenderings?.push(trc) } return { result: json } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 75b59559d8..5850f13ec8 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -26,7 +26,6 @@ import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource" import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore" import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter" -import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource" import ShowDataLayer from "../UI/Map/ShowDataLayer" import TitleHandler from "../Logic/Actors/TitleHandler" @@ -39,9 +38,10 @@ import Hotkeys from "../UI/Base/Hotkeys" import Translations from "../UI/i18n/Translations" import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource" -import SimpleFeatureSource from "../Logic/FeatureSource/Sources/SimpleFeatureSource" import { MenuState } from "./MenuState" import MetaTagging from "../Logic/MetaTagging" +import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator" +import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource" /** * @@ -65,7 +65,7 @@ export default class ThemeViewState implements SpecialVisualizationState { readonly selectedElement: UIEventSource readonly mapProperties: MapProperties & ExportableMap - readonly dataIsLoading: Store // TODO + readonly dataIsLoading: Store readonly guistate: MenuState readonly fullNodeDatabase?: FullNodeDatabaseSource // TODO @@ -80,6 +80,7 @@ export default class ThemeViewState implements SpecialVisualizationState { readonly geolocation: GeoLocationHandler readonly lastClickObject: WritableFeatureSource + constructor(layout: LayoutConfig) { this.layout = layout this.guistate = new MenuState(layout.id) @@ -121,49 +122,69 @@ export default class ThemeViewState implements SpecialVisualizationState { const self = this this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id) - this.newFeatures = new SimpleFeatureSource(undefined) - const layoutSource = new LayoutSource( - layout.layers, - this.featureSwitches, - this.newFeatures, - this.mapProperties, - this.osmConnection.Backend(), - (id) => self.layerState.filteredLayers.get(id).isDisplayed - ) - this.indexedFeatures = layoutSource - this.dataIsLoading = layoutSource.isLoading - const lastClick = (this.lastClickObject = new LastClickFeatureSource( - this.mapProperties.lastClickLocation, - this.layout - )) - const indexedElements = this.indexedFeatures - this.featureProperties = new FeaturePropertiesStore(indexedElements) - const perLayer = new PerLayerFeatureSourceSplitter( - Array.from(this.layerState.filteredLayers.values()).filter( - (l) => l.layerDef?.source !== null - ), - indexedElements, - { - constructStore: (features, layer) => new GeoIndexedStoreForLayer(features, layer), - handleLeftovers: (features) => { - console.warn( - "Got ", - features.length, - "leftover features, such as", - features[0].properties - ) - }, - } - ) - this.perLayer = perLayer.perLayer + { + /* Setup the layout source + * A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too + */ + + const layoutSource = new LayoutSource( + layout.layers, + this.featureSwitches, + this.mapProperties, + this.osmConnection.Backend(), + (id) => self.layerState.filteredLayers.get(id).isDisplayed + ) + this.indexedFeatures = layoutSource + this.dataIsLoading = layoutSource.isLoading + + const indexedElements = this.indexedFeatures + this.featureProperties = new FeaturePropertiesStore(indexedElements) + this.changes = new Changes( + { + dryRun: this.featureSwitches.featureSwitchIsTesting, + allElements: indexedElements, + featurePropertiesStore: this.featureProperties, + osmConnection: this.osmConnection, + historicalUserLocations: this.geolocation.historicalUserLocations, + }, + layout?.isLeftRightSensitive() ?? false + ) + this.newFeatures = new NewGeometryFromChangesFeatureSource( + this.changes, + indexedElements, + this.osmConnection.Backend() + ) + layoutSource.addSource(this.newFeatures) + + const perLayer = new PerLayerFeatureSourceSplitter( + Array.from(this.layerState.filteredLayers.values()).filter( + (l) => l.layerDef?.source !== null + ), + new ChangeGeometryApplicator(this.indexedFeatures, this.changes), + { + constructStore: (features, layer) => + new GeoIndexedStoreForLayer(features, layer), + handleLeftovers: (features) => { + console.warn( + "Got ", + features.length, + "leftover features, such as", + features[0].properties + ) + }, + } + ) + this.perLayer = perLayer.perLayer + } this.perLayer.forEach((fs) => { - new SaveFeatureSourceToLocalStorage( + /* TODO enable new SaveFeatureSourceToLocalStorage( + this.osmConnection.Backend(), fs.layer.layerDef.id, 15, fs, this.featureProperties - ) + )//*/ const filtered = new FilteringFeatureSource( fs.layer, @@ -187,16 +208,10 @@ export default class ThemeViewState implements SpecialVisualizationState { }) }) - this.changes = new Changes( - { - dryRun: this.featureSwitches.featureSwitchIsTesting, - allElements: indexedElements, - featurePropertiesStore: this.featureProperties, - osmConnection: this.osmConnection, - historicalUserLocations: this.geolocation.historicalUserLocations, - }, - layout?.isLeftRightSensitive() ?? false - ) + const lastClick = (this.lastClickObject = new LastClickFeatureSource( + this.mapProperties.lastClickLocation, + this.layout + )) this.initActors() this.drawSpecialLayers(lastClick) @@ -211,9 +226,13 @@ export default class ThemeViewState implements SpecialVisualizationState { private miscSetup() { this.userRelatedState.markLayoutAsVisited(this.layout) - this.selectedElement.addCallbackAndRunD(() => { - // As soon as we have a selected element, we clear it + this.selectedElement.addCallbackAndRunD((feature) => { + // As soon as we have a selected element, we clear the selected element // This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature + // The only exception is if the last element is the 'add_new'-button, as we don't want it to disappear + if (feature.properties.id === "last_click") { + return + } this.lastClickObject.features.setData([]) }) } diff --git a/UI/BigComponents/ActionButtons.ts b/UI/BigComponents/ActionButtons.ts index beba679860..8cd9fbccd1 100644 --- a/UI/BigComponents/ActionButtons.ts +++ b/UI/BigComponents/ActionButtons.ts @@ -11,7 +11,6 @@ import { Utils } from "../../Utils" import { MapillaryLink } from "./MapillaryLink" import { OpenIdEditor, OpenJosm } from "./CopyrightPanel" import Toggle from "../Input/Toggle" -import ScrollableFullScreen from "../Base/ScrollableFullScreen" import { DefaultGuiState } from "../DefaultGuiState" export class BackToThemeOverview extends Toggle { @@ -78,14 +77,6 @@ export class ActionButtons extends Combine { new OpenIdEditor(state, iconStyle), new MapillaryLink(state, iconStyle), new OpenJosm(state, iconStyle).SetClass("hidden-on-mobile"), - new SubtleButton( - Svg.translate_ui().SetStyle(iconStyle), - Translations.t.translations.activateButton - ).onClick(() => { - ScrollableFullScreen.collapse() - state.defaultGuiState.userInfoIsOpened.setData(true) - state.defaultGuiState.userInfoFocusedQuestion.setData("translation-mode") - }), ]) this.SetClass("block w-full link-no-underline") } diff --git a/UI/BigComponents/WaySplitMap.svelte b/UI/BigComponents/WaySplitMap.svelte new file mode 100644 index 0000000000..3f3a4c4f87 --- /dev/null +++ b/UI/BigComponents/WaySplitMap.svelte @@ -0,0 +1,104 @@ + +
+ +
diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index 4b28bdea75..b3de1bb380 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -90,6 +90,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { self.setAllowMoving(self.allowMoving.data) self.setAllowZooming(self.allowZooming.data) self.setMinzoom(self.minzoom.data) + self.setBounds(self.bounds.data) }) self.MoveMapToCurrentLoc(self.location.data) self.SetZoom(self.zoom.data) @@ -97,6 +98,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { self.setAllowMoving(self.allowMoving.data) self.setAllowZooming(self.allowZooming.data) self.setMinzoom(self.minzoom.data) + self.setBounds(self.bounds.data) this.updateStores() map.on("moveend", () => this.updateStores()) map.on("click", (e) => { @@ -238,18 +240,13 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { container.style.height = document.documentElement.clientHeight + "px" } - const markerCanvas: HTMLCanvasElement = await html2canvas( + await html2canvas( map.getCanvasContainer(), { backgroundColor: "#00000000", canvas: drawOn, } ) - const markers = await new Promise((resolve) => - markerCanvas.toBlob((data) => resolve(data)) - ) - console.log("Markers:", markers, markerCanvas) - // destinationCtx.drawImage(markerCanvas, 0, 0) } catch (e) { console.error(e) } finally { @@ -429,7 +426,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { private setBounds(bounds: BBox) { const map = this._maplibreMap.data - if (map === undefined) { + if (map === undefined || bounds === undefined) { return } const oldBounds = map.getBounds() diff --git a/UI/Map/MaplibreMap.svelte b/UI/Map/MaplibreMap.svelte index 7ebaa56b2b..a3dd0c198c 100644 --- a/UI/Map/MaplibreMap.svelte +++ b/UI/Map/MaplibreMap.svelte @@ -24,7 +24,7 @@ $map.resize(); }); }); - const styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=GvoVAJgu46I5rZapJuAy"; + const styleUrl = "https://api.maptiler.com/maps/15cc8f61-0353-4be6-b8da-13daea5f7432/style.json?key=GvoVAJgu46I5rZapJuAy";
self.update(features)) + features.features.addCallbackAndRunD(() => self.update(features.features)) } private calculatePropsFor( @@ -229,13 +229,23 @@ class LineRenderingLayer { return calculatedProps } - private async update(features: Feature[]) { + private currentSourceData + private async update(featureSource: Store) { const map = this._map while (!map.isStyleLoaded()) { await Utils.waitFor(100) } + + // After waiting 'till the map has loaded, the data might have changed already + // As such, we only now read the features from the featureSource and compare with the previously set data + const features = featureSource.data const src = map.getSource(this._layername) + if (this.currentSourceData === features) { + // Already up to date + return + } if (src === undefined) { + this.currentSourceData = features map.addSource(this._layername, { type: "geojson", data: { @@ -262,7 +272,6 @@ class LineRenderingLayer { }) map.on("click", linelayer, (e) => { - console.log("Click", e) e.originalEvent["consumed"] = true this._onClick(e.features[0]) }) @@ -297,9 +306,10 @@ class LineRenderingLayer { } }) } else { + this.currentSourceData = features src.setData({ type: "FeatureCollection", - features, + features: this.currentSourceData, }) } @@ -345,10 +355,21 @@ export default class ShowDataLayer { "ShowDataLayer.ts:range.json" ) private readonly _map: Store - private readonly _options: ShowDataLayerOptions & { layer: LayerConfig } + private readonly _options: ShowDataLayerOptions & { + layer: LayerConfig + drawMarkers?: true | boolean + drawLines?: true | boolean + } private readonly _popupCache: Map - constructor(map: Store, options: ShowDataLayerOptions & { layer: LayerConfig }) { + constructor( + map: Store, + options: ShowDataLayerOptions & { + layer: LayerConfig + drawMarkers?: true | boolean + drawLines?: true | boolean + } + ) { this._map = map this._options = options this._popupCache = new Map() @@ -405,28 +426,31 @@ export default class ShowDataLayer { selectedElement?.setData(feature) selectedLayer?.setData(this._options.layer) }) - for (let i = 0; i < this._options.layer.lineRendering.length; i++) { - const lineRenderingConfig = this._options.layer.lineRendering[i] - new LineRenderingLayer( - map, - features, - this._options.layer.id + "_linerendering_" + i, - lineRenderingConfig, - doShowLayer, - fetchStore, - onClick - ) + if (this._options.drawLines !== false) { + for (let i = 0; i < this._options.layer.lineRendering.length; i++) { + const lineRenderingConfig = this._options.layer.lineRendering[i] + new LineRenderingLayer( + map, + features, + this._options.layer.id + "_linerendering_" + i, + lineRenderingConfig, + doShowLayer, + fetchStore, + onClick + ) + } } - - for (const pointRenderingConfig of this._options.layer.mapRendering) { - new PointRenderingLayer( - map, - features, - pointRenderingConfig, - doShowLayer, - fetchStore, - onClick - ) + if (this._options.drawMarkers !== false) { + for (const pointRenderingConfig of this._options.layer.mapRendering) { + new PointRenderingLayer( + map, + features, + pointRenderingConfig, + doShowLayer, + fetchStore, + onClick + ) + } } features.features.addCallbackAndRunD((_) => this.zoomToCurrentFeatures(map)) } diff --git a/UI/Popup/AddNewPoint/AddNewPoint.svelte b/UI/Popup/AddNewPoint/AddNewPoint.svelte index 26b3975918..fef8dcf20b 100644 --- a/UI/Popup/AddNewPoint/AddNewPoint.svelte +++ b/UI/Popup/AddNewPoint/AddNewPoint.svelte @@ -83,19 +83,9 @@ snapOnto: snapToWay }); await state.changes.applyAction(newElementAction); + // The 'changes' should have created a new point, which added this into the 'featureProperties' const newId = newElementAction.newElementId; - state.newFeatures.features.data.push({ - type: "Feature", - properties: { - id: newId, - ...TagUtils.KVtoProperties(tags) - }, - geometry: { - type: "Point", - coordinates: [location.lon, location.lat] - } - }); - state.newFeatures.features.ping(); + const tagsStore = state.featureProperties.getStore(newId); { // Set some metainfo diff --git a/UI/Popup/CreateNewNote.svelte b/UI/Popup/CreateNewNote.svelte index 882c21ddeb..c57b763a16 100644 --- a/UI/Popup/CreateNewNote.svelte +++ b/UI/Popup/CreateNewNote.svelte @@ -58,6 +58,7 @@ ]) } }; + // Normally, the 'Changes' will generate the new element. The 'notes' are an exception to this state.newFeatures.features.data.push(feature); state.newFeatures.features.ping(); state.selectedElement?.setData(feature); diff --git a/UI/Popup/LoginButton.ts b/UI/Popup/LoginButton.ts index 93016aa3fe..866359469e 100644 --- a/UI/Popup/LoginButton.ts +++ b/UI/Popup/LoginButton.ts @@ -5,7 +5,7 @@ import { OsmConnection, OsmServiceState } from "../../Logic/Osm/OsmConnection" import { VariableUiElement } from "../Base/VariableUIElement" import Loading from "../Base/Loading" import Translations from "../i18n/Translations" -import { Store } from "../../Logic/UIEventSource" +import { ImmutableStore, Store } from "../../Logic/UIEventSource" import Combine from "../Base/Combine" import { Translation } from "../i18n/Translation" @@ -13,13 +13,13 @@ class LoginButton extends SubtleButton { constructor( text: BaseUIElement | string, state: { - osmConnection: OsmConnection + osmConnection?: OsmConnection }, icon?: BaseUIElement | string ) { super(icon ?? Svg.login_ui(), text) this.onClick(() => { - state.osmConnection.AttemptLogin() + state.osmConnection?.AttemptLogin() }) } } @@ -32,13 +32,16 @@ export class LoginToggle extends VariableUiElement { * If logging in is not possible for some reason, an appropriate error message is shown * * State contains the 'osmConnection' to work with + * @param el: Element to show when logged in + * @param text: To show on the login button. Default: nothing + * @param state: if no osmConnection is given, assumes test situation and will show 'el' as if logged in */ constructor( el: BaseUIElement, text: BaseUIElement | string, state: { - readonly osmConnection: OsmConnection - readonly featureSwitchUserbadge: Store + readonly osmConnection?: OsmConnection + readonly featureSwitchUserbadge?: Store } ) { const loading = new Loading("Trying to log in...") @@ -51,14 +54,14 @@ export class LoginToggle extends VariableUiElement { } super( - state.osmConnection.loadingStatus.map( + state.osmConnection?.loadingStatus?.map( (osmConnectionState) => { - if (state.featureSwitchUserbadge.data == false) { + if (state.featureSwitchUserbadge?.data == false) { // All features to login with are disabled return undefined } - const apiState = state.osmConnection.apiIsOnline.data + const apiState = state.osmConnection?.apiIsOnline?.data ?? "online" const apiTranslation = offlineModes[apiState] if (apiTranslation !== undefined) { return new Combine([ @@ -77,15 +80,15 @@ export class LoginToggle extends VariableUiElement { return el } - // Error! + // Fallback return new LoginButton( Translations.t.general.loginFailed, state, Svg.invalid_svg() ) }, - [state.featureSwitchUserbadge, state.osmConnection.apiIsOnline] - ) + [state.featureSwitchUserbadge, state.osmConnection?.apiIsOnline] + ) ?? new ImmutableStore(el) // ) } } diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index 8971b0e205..bf3ab9197b 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -2,33 +2,25 @@ import Toggle from "../Input/Toggle" import Svg from "../../Svg" import { UIEventSource } from "../../Logic/UIEventSource" import { SubtleButton } from "../Base/SubtleButton" -import { GeoOperations } from "../../Logic/GeoOperations" import Combine from "../Base/Combine" import { Button } from "../Base/Button" import Translations from "../i18n/Translations" import SplitAction from "../../Logic/Osm/Actions/SplitAction" import Title from "../Base/Title" -import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { BBox } from "../../Logic/BBox" -import split_point from "../../assets/layers/split_point/split_point.json" -import { OsmConnection } from "../../Logic/Osm/OsmConnection" -import { Changes } from "../../Logic/Osm/Changes" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import FilteredLayer from "../../Models/FilteredLayer" import BaseUIElement from "../BaseUIElement" import { VariableUiElement } from "../Base/VariableUIElement" -import ScrollableFullScreen from "../Base/ScrollableFullScreen" import { LoginToggle } from "./LoginButton" -import { SpecialVisualizationState } from "../SpecialVisualization" +import SvelteUIElement from "../Base/SvelteUIElement" +import WaySplitMap from "../BigComponents/WaySplitMap.svelte" +import { OsmObject } from "../../Logic/Osm/OsmObject" +import { Feature, Point } from "geojson" +import { WayId } from "../../Models/OsmFeature" +import { OsmConnection } from "../../Logic/Osm/OsmConnection" +import { Changes } from "../../Logic/Osm/Changes" +import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource" +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" export default class SplitRoadWizard extends Combine { - private static splitLayerStyling = new LayerConfig( - split_point, - "(BUILTIN) SplitRoadWizard.ts", - true - ) - public dialogIsOpened: UIEventSource /** @@ -37,20 +29,34 @@ export default class SplitRoadWizard extends Combine { * @param id: The id of the road to remove * @param state: the state of the application */ - constructor(id: string, state: SpecialVisualizationState) { + constructor( + id: WayId, + state: { + layout?: LayoutConfig + osmConnection?: OsmConnection + changes?: Changes + indexedFeatures?: IndexedFeatureSource + selectedElement?: UIEventSource + } + ) { const t = Translations.t.split // Contains the points on the road that are selected to split on - contains geojson points with extra properties such as 'location' with the distance along the linestring - const splitPoints = new UIEventSource<{ feature: any; freshness: Date }[]>([]) + const splitPoints = new UIEventSource[]>([]) const hasBeenSplit = new UIEventSource(false) // Toggle variable between show split button and map const splitClicked = new UIEventSource(false) - const leafletMap = new UIEventSource( - SplitRoadWizard.setupMapComponent(id, splitPoints, state) - ) + const leafletMap = new UIEventSource(undefined) + + function initMap() { + SplitRoadWizard.setupMapComponent(id, splitPoints).then((mapComponent) => + leafletMap.setData(mapComponent.SetClass("w-full h-80")) + ) + } + initMap() // Toggle between splitmap const splitButton = new SubtleButton( @@ -70,23 +76,19 @@ export default class SplitRoadWizard extends Combine { splitClicked.setData(false) const splitAction = new SplitAction( id, - splitPoints.data.map((ff) => ff.feature.geometry.coordinates), + splitPoints.data.map((ff) => <[number, number]>(ff.geometry).coordinates), { - theme: state?.layoutToUse?.id, + theme: state?.layout?.id, }, - 5, - (coordinates) => { - state.allElements.ContainingFeatures.get(id).geometry["coordinates"] = - coordinates - } + 5 ) - await state.changes.applyAction(splitAction) + await state.changes?.applyAction(splitAction) // We throw away the old map and splitpoints, and create a new map from scratch splitPoints.setData([]) - leafletMap.setData(SplitRoadWizard.setupMapComponent(id, splitPoints, state)) + initMap() // Close the popup. The contributor has to select a segment again to make sure they continue editing the correct segment; see #1219 - ScrollableFullScreen.collapse() + state.selectedElement?.setData(undefined) }) saveButton.SetClass("btn btn-primary mr-3") @@ -131,95 +133,14 @@ export default class SplitRoadWizard extends Combine { }) } - private static setupMapComponent( - id: string, - splitPoints: UIEventSource<{ feature: any; freshness: Date }[]>, - state: { - filteredLayers: UIEventSource - backgroundLayer: UIEventSource - featureSwitchIsTesting: UIEventSource - featureSwitchIsDebugging: UIEventSource - featureSwitchShowAllQuestions: UIEventSource - osmConnection: OsmConnection - featureSwitchUserbadge: UIEventSource - changes: Changes - layoutToUse: LayoutConfig - allElements: ElementStorage - } - ): BaseUIElement { - // Load the road with given id on the minimap - const roadElement = state.allElements.ContainingFeatures.get(id) - - // Minimap on which you can select the points to be splitted - const miniMap = Minimap.createMiniMap({ - background: state.backgroundLayer, - allowMoving: true, - leafletOptions: { - minZoom: 14, - }, + private static async setupMapComponent( + id: WayId, + splitPoints: UIEventSource + ): Promise { + const osmWay = await OsmObject.DownloadObjectAsync(id) + return new SvelteUIElement(WaySplitMap, { + osmWay, + splitPoints, }) - miniMap.SetStyle("width: 100%; height: 24rem").SetClass("rounded-xl overflow-hidden") - - miniMap.installBounds(BBox.get(roadElement).pad(0.25), false) - - // Define how a cut is displayed on the map - - // Datalayer displaying the road and the cut points (if any) - new ShowDataMultiLayer({ - features: StaticFeatureSource.fromGeojson([roadElement]), - layers: state.filteredLayers, - leafletMap: miniMap.leafletMap, - zoomToFeatures: true, - state, - }) - - new ShowDataLayer({ - features: new StaticFeatureSource(splitPoints), - leafletMap: miniMap.leafletMap, - zoomToFeatures: false, - layerToShow: SplitRoadWizard.splitLayerStyling, - state, - }) - /** - * Handles a click on the overleaf map. - * Finds the closest intersection with the road and adds a point there, ready to confirm the cut. - * @param coordinates Clicked location, [lon, lat] - */ - function onMapClick(coordinates) { - // First, we check if there is another, already existing point nearby - const points = splitPoints.data - .map((f, i) => [f.feature, i]) - .filter( - (p) => GeoOperations.distanceBetween(p[0].geometry.coordinates, coordinates) < 5 - ) - .map((p) => p[1]) - .sort((a, b) => a - b) - .reverse(/*Copy/derived list, inplace reverse is fine*/) - if (points.length > 0) { - for (const point of points) { - splitPoints.data.splice(point, 1) - } - splitPoints.ping() - return - } - - // Get nearest point on the road - const pointOnRoad = GeoOperations.nearestPoint(roadElement, coordinates) // pointOnRoad is a geojson - - // Update point properties to let it match the layer - pointOnRoad.properties["_split_point"] = "yes" - - // Add it to the list of all points and notify observers - splitPoints.data.push({ feature: pointOnRoad, freshness: new Date() }) // show the point on the data layer - splitPoints.ping() // not updated using .setData, so manually ping observers - } - - // When clicked, pass clicked location coordinates to onMapClick function - miniMap.leafletMap.addCallbackAndRunD((leafletMap) => - leafletMap.on("click", (mouseEvent: LeafletMouseEvent) => { - onMapClick([mouseEvent.latlng.lng, mouseEvent.latlng.lat]) - }) - ) - return miniMap } } diff --git a/UI/SpecialVisualization.ts b/UI/SpecialVisualization.ts index f249dfc10c..c30da739ca 100644 --- a/UI/SpecialVisualization.ts +++ b/UI/SpecialVisualization.ts @@ -1,18 +1,18 @@ -import { Store, UIEventSource } from "../Logic/UIEventSource" -import BaseUIElement from "./BaseUIElement" -import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" -import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" -import { OsmConnection } from "../Logic/Osm/OsmConnection" -import { Changes } from "../Logic/Osm/Changes" -import { ExportableMap, MapProperties } from "../Models/MapProperties" -import LayerState from "../Logic/State/LayerState" -import { Feature, Geometry } from "geojson" -import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" -import { MangroveIdentity } from "../Logic/Web/MangroveReviews" -import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" -import LayerConfig from "../Models/ThemeConfig/LayerConfig" -import FeatureSwitchState from "../Logic/State/FeatureSwitchState" -import { MenuState } from "../Models/MenuState" +import { Store, UIEventSource } from "../Logic/UIEventSource"; +import BaseUIElement from "./BaseUIElement"; +import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; +import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"; +import { OsmConnection } from "../Logic/Osm/OsmConnection"; +import { Changes } from "../Logic/Osm/Changes"; +import { ExportableMap, MapProperties } from "../Models/MapProperties"; +import LayerState from "../Logic/State/LayerState"; +import { Feature, Geometry } from "geojson"; +import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; +import { MangroveIdentity } from "../Logic/Web/MangroveReviews"; +import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"; +import LayerConfig from "../Models/ThemeConfig/LayerConfig"; +import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; +import { MenuState } from "../Models/MenuState"; /** * The state needed to render a special Visualisation. diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 9e1acdf160..3f5d119d62 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -77,8 +77,9 @@ import Lazy from "./Base/Lazy" import { CheckBox } from "./Input/Checkboxes" import Slider from "./Input/Slider" import DeleteWizard from "./Popup/DeleteWizard" -import { OsmId, OsmTags } from "../Models/OsmFeature" +import { OsmId, OsmTags, WayId } from "../Models/OsmFeature" import MoveWizard from "./Popup/MoveWizard" +import SplitRoadWizard from "./Popup/SplitRoadWizard" class NearbyImageVis implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -532,16 +533,17 @@ export default class SpecialVisualizations { args: [], constr( state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig + tagSource: UIEventSource> ): BaseUIElement { return new VariableUiElement( - // TODO tagSource .map((tags) => tags.id) - .map((id) => new FixedUiElement("TODO: enable splitting")) // new SplitRoadWizard(id, state)) + .map((id) => { + if (id.startsWith("way/")) { + return new SplitRoadWizard(id, state) + } + return undefined + }) ) }, }, diff --git a/assets/layers/split_road/split_road.json b/assets/layers/split_road/split_road.json new file mode 100644 index 0000000000..2b15e115fe --- /dev/null +++ b/assets/layers/split_road/split_road.json @@ -0,0 +1,21 @@ +{ + "id": "split_road", + "description": "Layer rendering the way to split in the 'splitRoadWizard'. This one is used instead of the variable rendering by the themes themselves, as they might not always be very visible", + "minzoom": 1, + "source": "special", + "name": null, + "title": null, + "mapRendering": [ + { + "location": [ + "point" + ], + "icon": "bug", + "iconSize": "30,30,center" + }, + { + "width": "8", + "color": "black" + } + ] +} diff --git a/assets/themes/cyclestreets/cyclestreets.json b/assets/themes/cyclestreets/cyclestreets.json index 7cc4efcb24..04b61a22c0 100644 --- a/assets/themes/cyclestreets/cyclestreets.json +++ b/assets/themes/cyclestreets/cyclestreets.json @@ -379,7 +379,7 @@ ], "overrideAll": { "allowSplit": true, - "tagRenderings+": [ + "+tagRenderings": [ { "id": "is_cyclestreet", "question": { @@ -723,4 +723,4 @@ } ] } -} \ No newline at end of file +} diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index dbc66652c3..c0df2b84b6 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -1061,6 +1061,10 @@ video { height: 10rem; } +.h-80 { + height: 20rem; +} + .max-h-20vh { max-height: 20vh; } @@ -1081,6 +1085,10 @@ video { min-height: 8rem; } +.w-full { + width: 100%; +} + .w-8 { width: 2rem; } @@ -1105,10 +1113,6 @@ video { width: 1.5rem; } -.w-full { - width: 100%; -} - .w-screen { width: 100vw; } diff --git a/test.html b/test.html index bc09c2964e..a84eca7e20 100644 --- a/test.html +++ b/test.html @@ -18,7 +18,7 @@ -
'maindiv' not attached
+
'maindiv' not attached
'extradiv' not attached
diff --git a/test.ts b/test.ts index 0b18421a94..b54dbd3aa2 100644 --- a/test.ts +++ b/test.ts @@ -9,6 +9,10 @@ import { UIEventSource } from "./Logic/UIEventSource" import { VariableUiElement } from "./UI/Base/VariableUIElement" import { FixedUiElement } from "./UI/Base/FixedUiElement" import Title from "./UI/Base/Title" +import WaySplitMap from "./UI/BigComponents/WaySplitMap.svelte" +import SvelteUIElement from "./UI/Base/SvelteUIElement" +import { OsmObject } from "./Logic/Osm/OsmObject" +import SplitRoadWizard from "./UI/Popup/SplitRoadWizard" function testspecial() { const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) @@ -41,5 +45,10 @@ function testinput() { } new Combine(els).SetClass("flex flex-col").AttachTo("maindiv") } -testinput() + +async function testWaySplit() { + new SplitRoadWizard("way/28717919", {}).SetClass("w-full h-full").AttachTo("maindiv") +} +testWaySplit().then((_) => console.log("inited")) +//testinput() // testspecial() From 1f9aacfb29bd44c67b8b49dc821da620538367e3 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 20 Apr 2023 03:58:31 +0200 Subject: [PATCH 049/257] Refactoring: move download functionality for OsmObjects into a new object --- Logic/Actors/SelectedElementTagsUpdater.ts | 9 +- Logic/Actors/SelectedFeatureHandler.ts | 9 +- .../NewGeometryFromChangesFeatureSource.ts | 28 +- .../FeatureSource/Sources/OsmFeatureSource.ts | 15 +- Logic/Osm/Actions/DeleteAction.ts | 7 +- Logic/Osm/Actions/RelationSplitHandler.ts | 47 +- Logic/Osm/Actions/SplitAction.ts | 11 +- Logic/Osm/Changes.ts | 555 +++++++++--------- Logic/Osm/OsmConnection.ts | 1 - Logic/Osm/OsmObject.ts | 214 +------ Logic/Osm/OsmObjectDownloader.ts | 152 +++++ Logic/SimpleMetaTagger.ts | 4 +- Models/ThemeViewState.ts | 4 + UI/BigComponents/ActionButtons.ts | 17 +- UI/Input/LocationInput.ts | 333 ----------- UI/Popup/AddNewPoint/AddNewPoint.svelte | 16 +- UI/Popup/DeleteWizard.ts | 36 +- UI/Popup/MoveWizard.ts | 4 +- UI/Popup/SplitRoadWizard.ts | 24 +- UI/SpecialVisualization.ts | 2 + scripts/CycleHighwayFix.ts | 3 +- .../OSM/Actions/RelationSplitHandler.spec.ts | 37 +- test/Logic/OSM/OsmObject.spec.ts | 6 +- 23 files changed, 633 insertions(+), 901 deletions(-) create mode 100644 Logic/Osm/OsmObjectDownloader.ts delete mode 100644 UI/Input/LocationInput.ts diff --git a/Logic/Actors/SelectedElementTagsUpdater.ts b/Logic/Actors/SelectedElementTagsUpdater.ts index c5b23d0f3c..8bfe511d82 100644 --- a/Logic/Actors/SelectedElementTagsUpdater.ts +++ b/Logic/Actors/SelectedElementTagsUpdater.ts @@ -3,13 +3,13 @@ */ import { UIEventSource } from "../UIEventSource" import { Changes } from "../Osm/Changes" -import { OsmObject } from "../Osm/OsmObject" import { OsmConnection } from "../Osm/OsmConnection" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import SimpleMetaTagger from "../SimpleMetaTagger" import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" import { Feature } from "geojson" import { OsmTags } from "../../Models/OsmFeature" +import OsmObjectDownloader from "../Osm/OsmObjectDownloader" export default class SelectedElementTagsUpdater { private static readonly metatags = new Set([ @@ -27,6 +27,7 @@ export default class SelectedElementTagsUpdater { changes: Changes osmConnection: OsmConnection layout: LayoutConfig + osmObjectDownloader: OsmObjectDownloader } constructor(state: { @@ -35,6 +36,7 @@ export default class SelectedElementTagsUpdater { changes: Changes osmConnection: OsmConnection layout: LayoutConfig + osmObjectDownloader: OsmObjectDownloader }) { this.state = state state.osmConnection.isLoggedIn.addCallbackAndRun((isLoggedIn) => { @@ -70,8 +72,8 @@ export default class SelectedElementTagsUpdater { return } try { - const latestTags = await OsmObject.DownloadPropertiesOf(id) - if (latestTags === "deleted") { + const osmObject = await state.osmObjectDownloader.DownloadObjectAsync(id) + if (osmObject === "deleted") { console.warn("The current selected element has been deleted upstream!") const currentTagsSource = state.featureProperties.getStore(id) if (currentTagsSource.data["_deleted"] === "yes") { @@ -81,6 +83,7 @@ export default class SelectedElementTagsUpdater { currentTagsSource.ping() return } + const latestTags = osmObject.tags this.applyUpdate(latestTags, id) console.log("Updated", id) } catch (e) { diff --git a/Logic/Actors/SelectedFeatureHandler.ts b/Logic/Actors/SelectedFeatureHandler.ts index 3591bc4eb4..3d30a9ca18 100644 --- a/Logic/Actors/SelectedFeatureHandler.ts +++ b/Logic/Actors/SelectedFeatureHandler.ts @@ -1,10 +1,10 @@ import { UIEventSource } from "../UIEventSource" -import { OsmObject } from "../Osm/OsmObject" import Loc from "../../Models/Loc" import { ElementStorage } from "../ElementStorage" import FeaturePipeline from "../FeatureSource/FeaturePipeline" import { GeoOperations } from "../GeoOperations" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" +import OsmObjectDownloader from "../Osm/OsmObjectDownloader" /** * Makes sure the hash shows the selected element and vice-versa. @@ -26,6 +26,7 @@ export default class SelectedFeatureHandler { allElements: ElementStorage locationControl: UIEventSource layoutToUse: LayoutConfig + objectDownloader: OsmObjectDownloader } constructor( @@ -36,6 +37,7 @@ export default class SelectedFeatureHandler { featurePipeline: FeaturePipeline locationControl: UIEventSource layoutToUse: LayoutConfig + objectDownloader: OsmObjectDownloader } ) { this.hash = hash @@ -65,8 +67,11 @@ export default class SelectedFeatureHandler { return } - OsmObject.DownloadObjectAsync(hash).then((obj) => { + this.state.objectDownloader.DownloadObjectAsync(hash).then((obj) => { try { + if (obj === "deleted") { + return + } console.log("Downloaded selected object from OSM-API for initial load: ", hash) const geojson = obj.asGeoJson() this.state.allElements.addOrGetElement(geojson) diff --git a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts index 44b6e13f58..c3cc099d8a 100644 --- a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts +++ b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts @@ -1,10 +1,11 @@ import { Changes } from "../../Osm/Changes" -import { OsmNode, OsmObject, OsmRelation, OsmWay } from "../../Osm/OsmObject" +import { OsmNode, OsmRelation, OsmWay } from "../../Osm/OsmObject" import { IndexedFeatureSource, WritableFeatureSource } from "../FeatureSource" import { UIEventSource } from "../../UIEventSource" import { ChangeDescription } from "../../Osm/Actions/ChangeDescription" import { OsmId, OsmTags } from "../../../Models/OsmFeature" import { Feature } from "geojson" +import OsmObjectDownloader from "../../Osm/OsmObjectDownloader" export class NewGeometryFromChangesFeatureSource implements WritableFeatureSource { // This class name truly puts the 'Java' into 'Javascript' @@ -21,7 +22,7 @@ export class NewGeometryFromChangesFeatureSource implements WritableFeatureSourc const seenChanges = new Set() const features = this.features.data const self = this - + const backend = changes.backend changes.pendingChanges.stabilized(100).addCallbackAndRunD((changes) => { if (changes.length === 0) { return @@ -58,15 +59,20 @@ export class NewGeometryFromChangesFeatureSource implements WritableFeatureSourc } console.debug("Detected a reused point") // The 'allElementsStore' does _not_ have this point yet, so we have to create it - OsmObject.DownloadObjectAsync(change.type + "/" + change.id).then((feat) => { - console.log("Got the reused point:", feat) - for (const kv of change.tags) { - feat.tags[kv.k] = kv.v - } - const geojson = feat.asGeoJson() - self.features.data.push(geojson) - self.features.ping() - }) + new OsmObjectDownloader(backend) + .DownloadObjectAsync(change.type + "/" + change.id) + .then((feat) => { + console.log("Got the reused point:", feat) + if (feat === "deleted") { + throw "Panic: snapping to a point, but this point has been deleted in the meantime" + } + for (const kv of change.tags) { + feat.tags[kv.k] = kv.v + } + const geojson = feat.asGeoJson() + self.features.data.push(geojson) + self.features.ping() + }) continue } else if (change.id < 0 && change.changes === undefined) { // The geometry is not described - not a new point diff --git a/Logic/FeatureSource/Sources/OsmFeatureSource.ts b/Logic/FeatureSource/Sources/OsmFeatureSource.ts index 91a819e2be..0c8949f7c1 100644 --- a/Logic/FeatureSource/Sources/OsmFeatureSource.ts +++ b/Logic/FeatureSource/Sources/OsmFeatureSource.ts @@ -4,9 +4,9 @@ import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" import { Tiles } from "../../../Models/TileRange" import { BBox } from "../../BBox" import { TagsFilter } from "../../Tags/TagsFilter" -import { OsmObject } from "../../Osm/OsmObject" import { Feature } from "geojson" import FeatureSourceMerger from "../Sources/FeatureSourceMerger" +import OsmObjectDownloader from "../../Osm/OsmObjectDownloader" /** * If a tile is needed (requested via the UIEventSource in the constructor), will download the appropriate tile and pass it via 'handleTile' @@ -101,7 +101,17 @@ export default class OsmFeatureSource extends FeatureSourceMerger { // This member is missing. We redownload the entire relation instead console.debug("Fetching incomplete relation " + feature.properties.id) - return (await OsmObject.DownloadObjectAsync(feature.properties.id)).asGeoJson() + const dfeature = await new OsmObjectDownloader(this._backend).DownloadObjectAsync( + feature.properties.id + ) + if (dfeature === "deleted") { + console.warn( + "This relation has been deleted in the meantime: ", + feature.properties.id + ) + return + } + return dfeature.asGeoJson() } return feature } @@ -149,6 +159,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger { for (let i = 0; i < features.length; i++) { features[i] = await this.patchIncompleteRelations(features[i], osmJson) } + features = Utils.NoNull(features) features.forEach((f) => { f.properties["_backend"] = this._backend }) diff --git a/Logic/Osm/Actions/DeleteAction.ts b/Logic/Osm/Actions/DeleteAction.ts index 9ab5fab49c..97a8650564 100644 --- a/Logic/Osm/Actions/DeleteAction.ts +++ b/Logic/Osm/Actions/DeleteAction.ts @@ -8,6 +8,7 @@ import { And } from "../../Tags/And" import { Tag } from "../../Tags/Tag" import { OsmId } from "../../../Models/OsmFeature" import { Utils } from "../../../Utils" +import OsmObjectDownloader from "../OsmObjectDownloader"; export default class DeleteAction extends OsmChangeAction { private readonly _softDeletionTags: TagsFilter @@ -71,8 +72,12 @@ export default class DeleteAction extends OsmChangeAction { changes: Changes, object?: OsmObject ): Promise { - const osmObject = object ?? (await OsmObject.DownloadObjectAsync(this._id)) + const osmObject = object ?? (await new OsmObjectDownloader(changes.backend, changes).DownloadObjectAsync(this._id)) + if(osmObject === "deleted"){ + // already deleted in the meantime - no more changes necessary + return [] + } if (this._hardDelete) { return [ { diff --git a/Logic/Osm/Actions/RelationSplitHandler.ts b/Logic/Osm/Actions/RelationSplitHandler.ts index 1fd6749b84..3cdc6cc43b 100644 --- a/Logic/Osm/Actions/RelationSplitHandler.ts +++ b/Logic/Osm/Actions/RelationSplitHandler.ts @@ -1,7 +1,8 @@ import OsmChangeAction from "./OsmChangeAction" import { Changes } from "../Changes" import { ChangeDescription } from "./ChangeDescription" -import { OsmObject, OsmRelation, OsmWay } from "../OsmObject" +import { OsmRelation, OsmWay } from "../OsmObject" +import OsmObjectDownloader from "../OsmObjectDownloader" export interface RelationSplitInput { relation: OsmRelation @@ -14,11 +15,13 @@ export interface RelationSplitInput { abstract class AbstractRelationSplitHandler extends OsmChangeAction { protected readonly _input: RelationSplitInput protected readonly _theme: string + protected readonly _objectDownloader: OsmObjectDownloader - constructor(input: RelationSplitInput, theme: string) { + constructor(input: RelationSplitInput, theme: string, objectDownloader: OsmObjectDownloader) { super("relation/" + input.relation.id, false) this._input = input this._theme = theme + this._objectDownloader = objectDownloader } /** @@ -33,7 +36,9 @@ abstract class AbstractRelationSplitHandler extends OsmChangeAction { return member.ref } if (member.type === "way") { - const osmWay = await OsmObject.DownloadObjectAsync("way/" + member.ref) + const osmWay = ( + await this._objectDownloader.DownloadObjectAsync("way/" + member.ref) + ) const nodes = osmWay.nodes if (first) { return nodes[0] @@ -52,26 +57,30 @@ abstract class AbstractRelationSplitHandler extends OsmChangeAction { * When a way is split and this way is part of a relation, the relation should be updated too to have the new segment if relevant. */ export default class RelationSplitHandler extends AbstractRelationSplitHandler { - constructor(input: RelationSplitInput, theme: string) { - super(input, theme) + constructor(input: RelationSplitInput, theme: string, objectDownloader: OsmObjectDownloader) { + super(input, theme, objectDownloader) } async CreateChangeDescriptions(changes: Changes): Promise { if (this._input.relation.tags["type"] === "restriction") { // This is a turn restriction - return new TurnRestrictionRSH(this._input, this._theme).CreateChangeDescriptions( - changes - ) + return new TurnRestrictionRSH( + this._input, + this._theme, + this._objectDownloader + ).CreateChangeDescriptions(changes) } - return new InPlaceReplacedmentRTSH(this._input, this._theme).CreateChangeDescriptions( - changes - ) + return new InPlaceReplacedmentRTSH( + this._input, + this._theme, + this._objectDownloader + ).CreateChangeDescriptions(changes) } } export class TurnRestrictionRSH extends AbstractRelationSplitHandler { - constructor(input: RelationSplitInput, theme: string) { - super(input, theme) + constructor(input: RelationSplitInput, theme: string, objectDownloader: OsmObjectDownloader) { + super(input, theme, objectDownloader) } public async CreateChangeDescriptions(changes: Changes): Promise { @@ -91,9 +100,11 @@ export class TurnRestrictionRSH extends AbstractRelationSplitHandler { if (selfMember.role === "via") { // A via way can be replaced in place - return new InPlaceReplacedmentRTSH(this._input, this._theme).CreateChangeDescriptions( - changes - ) + return new InPlaceReplacedmentRTSH( + this._input, + this._theme, + this._objectDownloader + ).CreateChangeDescriptions(changes) } // We have to keep only the way with a common point with the rest of the relation @@ -166,8 +177,8 @@ export class TurnRestrictionRSH extends AbstractRelationSplitHandler { * Note that the feature might appear multiple times. */ export class InPlaceReplacedmentRTSH extends AbstractRelationSplitHandler { - constructor(input: RelationSplitInput, theme: string) { - super(input, theme) + constructor(input: RelationSplitInput, theme: string, objectDownloader: OsmObjectDownloader) { + super(input, theme, objectDownloader) } async CreateChangeDescriptions(changes: Changes): Promise { diff --git a/Logic/Osm/Actions/SplitAction.ts b/Logic/Osm/Actions/SplitAction.ts index 6c8166220d..7b15b57eec 100644 --- a/Logic/Osm/Actions/SplitAction.ts +++ b/Logic/Osm/Actions/SplitAction.ts @@ -5,6 +5,7 @@ import OsmChangeAction from "./OsmChangeAction" import { ChangeDescription } from "./ChangeDescription" import RelationSplitHandler from "./RelationSplitHandler" import { Feature, LineString } from "geojson" +import OsmObjectDownloader from "../OsmObjectDownloader" interface SplitInfo { originalIndex?: number // or negative for new elements @@ -61,7 +62,9 @@ export default class SplitAction extends OsmChangeAction { } async CreateChangeDescriptions(changes: Changes): Promise { - const originalElement = await OsmObject.DownloadObjectAsync(this.wayId) + const originalElement = ( + await new OsmObjectDownloader(changes.backend, changes).DownloadObjectAsync(this.wayId) + ) const originalNodes = originalElement.nodes // First, calculate the splitpoints and remove points close to one another @@ -172,7 +175,8 @@ export default class SplitAction extends OsmChangeAction { // At last, we still have to check that we aren't part of a relation... // At least, the order of the ways is identical, so we can keep the same roles - const relations = await OsmObject.DownloadReferencingRelations(this.wayId) + const downloader = new OsmObjectDownloader(changes.backend, changes) + const relations = await downloader.DownloadReferencingRelations(this.wayId) for (const relation of relations) { const changDescrs = await new RelationSplitHandler( { @@ -182,7 +186,8 @@ export default class SplitAction extends OsmChangeAction { allWaysNodesInOrder: allWaysNodesInOrder, originalWayId: originalElement.id, }, - this._meta.theme + this._meta.theme, + downloader ).CreateChangeDescriptions(changes) changeDescription.push(...changDescrs) } diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index b7580ad772..b6dc34d9a9 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -12,6 +12,7 @@ import { GeoOperations } from "../GeoOperations" import { ChangesetHandler, ChangesetTag } from "./ChangesetHandler" import { OsmConnection } from "./OsmConnection" import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" +import OsmObjectDownloader from "./OsmObjectDownloader" /** * Handles all changes made to OSM. @@ -23,10 +24,10 @@ export class Changes { public readonly allChanges = new UIEventSource(undefined) public readonly state: { allElements?: IndexedFeatureSource; osmConnection: OsmConnection } public readonly extraComment: UIEventSource = new UIEventSource(undefined) - + public readonly backend: string private readonly historicalUserLocations?: FeatureSource private _nextId: number = -1 // Newly assigned ID's are negative - private readonly isUploading = new UIEventSource(false) + public readonly isUploading = new UIEventSource(false) private readonly previouslyCreated: OsmObject[] = [] private readonly _leftRightSensitive: boolean private readonly _changesetHandler: ChangesetHandler @@ -47,6 +48,7 @@ export class Changes { // If a pending change contains a negative ID, we save that this._nextId = Math.min(-1, ...(this.pendingChanges.data?.map((pch) => pch.id) ?? [])) this.state = state + this.backend = state.osmConnection.Backend() this._changesetHandler = new ChangesetHandler( state.dryRun, state.osmConnection, @@ -149,274 +151,6 @@ export class Changes { this.allChanges.ping() } - private calculateDistanceToChanges( - change: OsmChangeAction, - changeDescriptions: ChangeDescription[] - ) { - const locations = this.historicalUserLocations?.features?.data - if (locations === undefined) { - // No state loaded or no locations -> we can't calculate... - return - } - if (!change.trackStatistics) { - // Probably irrelevant, such as a new helper node - return - } - - const now = new Date() - const recentLocationPoints = locations - .filter((feat) => feat.geometry.type === "Point") - .filter((feat) => { - const visitTime = new Date( - ((feat.properties)).date - ) - // In seconds - const diff = (now.getTime() - visitTime.getTime()) / 1000 - return diff < Constants.nearbyVisitTime - }) - if (recentLocationPoints.length === 0) { - // Probably no GPS enabled/no fix - return - } - - // The applicable points, contain information in their properties about location, time and GPS accuracy - // They are all GeoLocationPointProperties - // We walk every change and determine the closest distance possible - // Only if the change itself does _not_ contain any coordinates, we fall back and search the original feature in the state - - const changedObjectCoordinates: [number, number][] = [] - - { - const feature = this.state.allElements?.featuresById?.data.get(change.mainObjectId) - if (feature !== undefined) { - changedObjectCoordinates.push(GeoOperations.centerpointCoordinates(feature)) - } - } - - for (const changeDescription of changeDescriptions) { - const chng: - | { lat: number; lon: number } - | { coordinates: [number, number][] } - | { members } = changeDescription.changes - if (chng === undefined) { - continue - } - if (chng["lat"] !== undefined) { - changedObjectCoordinates.push([chng["lat"], chng["lon"]]) - } - if (chng["coordinates"] !== undefined) { - changedObjectCoordinates.push(...chng["coordinates"]) - } - } - - return Math.min( - ...changedObjectCoordinates.map((coor) => - Math.min( - ...recentLocationPoints.map((gpsPoint) => { - const otherCoor = GeoOperations.centerpointCoordinates(gpsPoint) - return GeoOperations.distanceBetween(coor, otherCoor) - }) - ) - ) - ) - } - - /** - * UPload the selected changes to OSM. - * Returns 'true' if successfull and if they can be removed - */ - private async flushSelectChanges( - pending: ChangeDescription[], - openChangeset: UIEventSource - ): Promise { - const self = this - const neededIds = Changes.GetNeededIds(pending) - - const osmObjects = Utils.NoNull( - await Promise.all( - neededIds.map(async (id) => - OsmObject.DownloadObjectAsync(id).catch((e) => { - console.error( - "Could not download OSM-object", - id, - " dropping it from the changes (" + e + ")" - ) - pending = pending.filter((ch) => ch.type + "/" + ch.id !== id) - return undefined - }) - ) - ) - ) - - if (this._leftRightSensitive) { - osmObjects.forEach((obj) => SimpleMetaTagger.removeBothTagging(obj.tags)) - } - - console.log("Got the fresh objects!", osmObjects, "pending: ", pending) - if (pending.length == 0) { - console.log("No pending changes...") - return true - } - - const perType = Array.from( - Utils.Hist( - pending - .filter( - (descr) => - descr.meta.changeType !== undefined && descr.meta.changeType !== null - ) - .map((descr) => descr.meta.changeType) - ), - ([key, count]) => ({ - key: key, - value: count, - 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, - })) - - const distances = Utils.NoNull(pending.map((descr) => descr.meta.distanceToObject)) - distances.sort((a, b) => a - b) - const perBinCount = Constants.distanceToChangeObjectBins.map((_) => 0) - - let j = 0 - const maxDistances = Constants.distanceToChangeObjectBins - for (let i = 0; i < maxDistances.length; i++) { - const maxDistance = maxDistances[i] - // distances is sorted in ascending order, so as soon as one is to big, all the resting elements will be bigger too - while (j < distances.length && distances[j] < maxDistance) { - perBinCount[i]++ - j++ - } - } - - const perBinMessage = Utils.NoNull( - perBinCount.map((count, i) => { - if (count === 0) { - return undefined - } - const maxD = maxDistances[i] - let key = `change_within_${maxD}m` - if (maxD === Number.MAX_VALUE) { - key = `change_over_${maxDistances[i - 1]}m` - } - return { - key, - value: count, - aggregate: true, - } - }) - ) - - // This method is only called with changedescriptions for this theme - const theme = pending[0].meta.theme - let comment = "Adding data with #MapComplete for theme #" + theme - if (this.extraComment.data !== undefined) { - comment += "\n\n" + this.extraComment.data - } - - const metatags: ChangesetTag[] = [ - { - key: "comment", - value: comment, - }, - { - key: "theme", - value: theme, - }, - ...perType, - ...motivations, - ...perBinMessage, - ] - - await this._changesetHandler.UploadChangeset( - (csId, remappings) => { - if (remappings.size > 0) { - console.log("Rewriting pending changes from", pending, "with", remappings) - pending = pending.map((ch) => ChangeDescriptionTools.rewriteIds(ch, remappings)) - console.log("Result is", pending) - } - const changes: { - newObjects: OsmObject[] - modifiedObjects: OsmObject[] - deletedObjects: OsmObject[] - } = self.CreateChangesetObjects(pending, osmObjects) - return Changes.createChangesetFor("" + csId, changes) - }, - metatags, - openChangeset - ) - - console.log("Upload successfull!") - return true - } - - private async flushChangesAsync(): Promise { - const self = this - try { - // At last, we build the changeset and upload - const pending = self.pendingChanges.data - - const pendingPerTheme = new Map() - for (const changeDescription of pending) { - const theme = changeDescription.meta.theme - if (!pendingPerTheme.has(theme)) { - pendingPerTheme.set(theme, []) - } - pendingPerTheme.get(theme).push(changeDescription) - } - - const successes = await Promise.all( - Array.from(pendingPerTheme, async ([theme, pendingChanges]) => { - try { - const openChangeset = this.state.osmConnection - .GetPreference("current-open-changeset-" + theme) - .sync( - (str) => { - const n = Number(str) - if (isNaN(n)) { - return undefined - } - return n - }, - [], - (n) => "" + n - ) - console.log( - "Using current-open-changeset-" + - theme + - " from the preferences, got " + - openChangeset.data - ) - - return await self.flushSelectChanges(pendingChanges, openChangeset) - } catch (e) { - console.error("Could not upload some changes:", e) - return false - } - }) - ) - - if (!successes.some((s) => s == false)) { - // All changes successfull, we clear the data! - this.pendingChanges.setData([]) - } - } catch (e) { - console.error( - "Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those", - e - ) - self.pendingChanges.setData([]) - } finally { - self.isUploading.setData(false) - } - } - public CreateChangesetObjects( changes: ChangeDescription[], downloadedOsmObjects: OsmObject[] @@ -584,4 +318,285 @@ export class Changes { ) return result } + + private calculateDistanceToChanges( + change: OsmChangeAction, + changeDescriptions: ChangeDescription[] + ) { + const locations = this.historicalUserLocations?.features?.data + if (locations === undefined) { + // No state loaded or no locations -> we can't calculate... + return + } + if (!change.trackStatistics) { + // Probably irrelevant, such as a new helper node + return + } + + const now = new Date() + const recentLocationPoints = locations + .filter((feat) => feat.geometry.type === "Point") + .filter((feat) => { + const visitTime = new Date( + ((feat.properties)).date + ) + // In seconds + const diff = (now.getTime() - visitTime.getTime()) / 1000 + return diff < Constants.nearbyVisitTime + }) + if (recentLocationPoints.length === 0) { + // Probably no GPS enabled/no fix + return + } + + // The applicable points, contain information in their properties about location, time and GPS accuracy + // They are all GeoLocationPointProperties + // We walk every change and determine the closest distance possible + // Only if the change itself does _not_ contain any coordinates, we fall back and search the original feature in the state + + const changedObjectCoordinates: [number, number][] = [] + + { + const feature = this.state.allElements?.featuresById?.data.get(change.mainObjectId) + if (feature !== undefined) { + changedObjectCoordinates.push(GeoOperations.centerpointCoordinates(feature)) + } + } + + for (const changeDescription of changeDescriptions) { + const chng: + | { lat: number; lon: number } + | { coordinates: [number, number][] } + | { members } = changeDescription.changes + if (chng === undefined) { + continue + } + if (chng["lat"] !== undefined) { + changedObjectCoordinates.push([chng["lat"], chng["lon"]]) + } + if (chng["coordinates"] !== undefined) { + changedObjectCoordinates.push(...chng["coordinates"]) + } + } + + return Math.min( + ...changedObjectCoordinates.map((coor) => + Math.min( + ...recentLocationPoints.map((gpsPoint) => { + const otherCoor = GeoOperations.centerpointCoordinates(gpsPoint) + return GeoOperations.distanceBetween(coor, otherCoor) + }) + ) + ) + ) + } + + /** + * Upload the selected changes to OSM. + * Returns 'true' if successful and if they can be removed + */ + private async flushSelectChanges( + pending: ChangeDescription[], + openChangeset: UIEventSource + ): Promise { + const self = this + const neededIds = Changes.GetNeededIds(pending) + // We _do not_ pass in the Changes object itself - we want the data from OSM directly in order to apply the changes + const downloader = new OsmObjectDownloader(this.backend, undefined) + let osmObjects = await Promise.all<{ id: string; osmObj: OsmObject | "deleted" }>( + neededIds.map(async (id) => { + try { + const osmObj = await downloader.DownloadObjectAsync(id) + return { id, osmObj } + } catch (e) { + console.error( + "Could not download OSM-object", + id, + " dropping it from the changes (" + e + ")" + ) + return undefined + } + }) + ) + + osmObjects = Utils.NoNull(osmObjects) + + for (const { osmObj, id } of osmObjects) { + if (osmObj === "deleted") { + pending = pending.filter((ch) => ch.type + "/" + ch.id !== id) + } + } + + const objects = osmObjects + .filter((obj) => obj.osmObj !== "deleted") + .map((obj) => obj.osmObj) + + if (this._leftRightSensitive) { + objects.forEach((obj) => SimpleMetaTagger.removeBothTagging(obj.tags)) + } + + console.log("Got the fresh objects!", objects, "pending: ", pending) + if (pending.length == 0) { + console.log("No pending changes...") + return true + } + + const perType = Array.from( + Utils.Hist( + pending + .filter( + (descr) => + descr.meta.changeType !== undefined && descr.meta.changeType !== null + ) + .map((descr) => descr.meta.changeType) + ), + ([key, count]) => ({ + key: key, + value: count, + 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, + })) + + const distances = Utils.NoNull(pending.map((descr) => descr.meta.distanceToObject)) + distances.sort((a, b) => a - b) + const perBinCount = Constants.distanceToChangeObjectBins.map((_) => 0) + + let j = 0 + const maxDistances = Constants.distanceToChangeObjectBins + for (let i = 0; i < maxDistances.length; i++) { + const maxDistance = maxDistances[i] + // distances is sorted in ascending order, so as soon as one is to big, all the resting elements will be bigger too + while (j < distances.length && distances[j] < maxDistance) { + perBinCount[i]++ + j++ + } + } + + const perBinMessage = Utils.NoNull( + perBinCount.map((count, i) => { + if (count === 0) { + return undefined + } + const maxD = maxDistances[i] + let key = `change_within_${maxD}m` + if (maxD === Number.MAX_VALUE) { + key = `change_over_${maxDistances[i - 1]}m` + } + return { + key, + value: count, + aggregate: true, + } + }) + ) + + // This method is only called with changedescriptions for this theme + const theme = pending[0].meta.theme + let comment = "Adding data with #MapComplete for theme #" + theme + if (this.extraComment.data !== undefined) { + comment += "\n\n" + this.extraComment.data + } + + const metatags: ChangesetTag[] = [ + { + key: "comment", + value: comment, + }, + { + key: "theme", + value: theme, + }, + ...perType, + ...motivations, + ...perBinMessage, + ] + + await this._changesetHandler.UploadChangeset( + (csId, remappings) => { + if (remappings.size > 0) { + console.log("Rewriting pending changes from", pending, "with", remappings) + pending = pending.map((ch) => ChangeDescriptionTools.rewriteIds(ch, remappings)) + console.log("Result is", pending) + } + const changes: { + newObjects: OsmObject[] + modifiedObjects: OsmObject[] + deletedObjects: OsmObject[] + } = self.CreateChangesetObjects(pending, objects) + return Changes.createChangesetFor("" + csId, changes) + }, + metatags, + openChangeset + ) + + console.log("Upload successfull!") + return true + } + + private async flushChangesAsync(): Promise { + const self = this + try { + // At last, we build the changeset and upload + const pending = self.pendingChanges.data + + const pendingPerTheme = new Map() + for (const changeDescription of pending) { + const theme = changeDescription.meta.theme + if (!pendingPerTheme.has(theme)) { + pendingPerTheme.set(theme, []) + } + pendingPerTheme.get(theme).push(changeDescription) + } + + const successes = await Promise.all( + Array.from(pendingPerTheme, async ([theme, pendingChanges]) => { + try { + const openChangeset = this.state.osmConnection + .GetPreference("current-open-changeset-" + theme) + .sync( + (str) => { + const n = Number(str) + if (isNaN(n)) { + return undefined + } + return n + }, + [], + (n) => "" + n + ) + console.log( + "Using current-open-changeset-" + + theme + + " from the preferences, got " + + openChangeset.data + ) + + return await self.flushSelectChanges(pendingChanges, openChangeset) + } catch (e) { + console.error("Could not upload some changes:", e) + return false + } + }) + ) + + if (!successes.some((s) => s == false)) { + // All changes successfull, we clear the data! + this.pendingChanges.setData([]) + } + } catch (e) { + console.error( + "Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those", + e + ) + self.pendingChanges.setData([]) + } finally { + self.isUploading.setData(false) + } + } } diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 745daa6454..ee9f3510c0 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -82,7 +82,6 @@ export class OsmConnection { OsmConnection.oauth_configs[options.osmConfiguration ?? "osm"] ?? OsmConnection.oauth_configs.osm console.debug("Using backend", this._oauth_config.url) - OsmObject.SetBackendUrl(this._oauth_config.url + "/") this._iframeMode = Utils.runningFromConsole ? false : window !== window.top this.userDetails = new UIEventSource( diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index 11996a92c6..3367db4e8f 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -1,17 +1,13 @@ import { Utils } from "../../Utils" import polygon_features from "../../assets/polygon-features.json" -import { Store, UIEventSource } from "../UIEventSource" -import { BBox } from "../BBox" import OsmToGeoJson from "osmtogeojson" -import { NodeId, OsmFeature, OsmId, OsmTags, RelationId, WayId } from "../../Models/OsmFeature" +import { OsmFeature, OsmId, OsmTags, WayId } from "../../Models/OsmFeature" import { Feature, LineString, Polygon } from "geojson" export abstract class OsmObject { private static defaultBackend = "https://www.openstreetmap.org/" protected static backendURL = OsmObject.defaultBackend private static polygonFeatures = OsmObject.constructPolygonFeatures() - private static objectCache = new Map>() - private static historyCache = new Map>() type: "node" | "way" | "relation" id: number /** @@ -31,190 +27,6 @@ export abstract class OsmObject { } } - public static SetBackendUrl(url: string) { - if (!url.endsWith("/")) { - throw "Backend URL must end with a '/'" - } - if (!url.startsWith("http")) { - throw "Backend URL must begin with http" - } - this.backendURL = url - } - - public static DownloadObject(id: NodeId, forceRefresh?: boolean): Store - public static DownloadObject(id: RelationId, forceRefresh?: boolean): Store - public static DownloadObject(id: WayId, forceRefresh?: boolean): Store - public static DownloadObject(id: string, forceRefresh: boolean = false): Store { - let src: UIEventSource - if (OsmObject.objectCache.has(id)) { - src = OsmObject.objectCache.get(id) - if (forceRefresh) { - src.setData(undefined) - } else { - return src - } - } else { - src = UIEventSource.FromPromise(OsmObject.DownloadObjectAsync(id)) - } - - OsmObject.objectCache.set(id, src) - return src - } - - static async DownloadPropertiesOf(id: string): Promise { - const splitted = id.split("/") - const idN = Number(splitted[1]) - if (idN < 0) { - return undefined - } - - const url = `${OsmObject.backendURL}api/0.6/${id}` - const rawData = await Utils.downloadJsonCachedAdvanced(url, 1000) - if (rawData["error"] !== undefined && rawData["statuscode"] === 410) { - return "deleted" - } - // Tags is undefined if the element does not have any tags - return rawData["content"].elements[0].tags ?? {} - } - - static async DownloadObjectAsync( - id: NodeId, - maxCacheAgeInSecs?: number - ): Promise - static async DownloadObjectAsync( - id: WayId, - maxCacheAgeInSecs?: number - ): Promise - static async DownloadObjectAsync( - id: RelationId, - maxCacheAgeInSecs?: number - ): Promise - static async DownloadObjectAsync( - id: OsmId, - maxCacheAgeInSecs?: number - ): Promise - static async DownloadObjectAsync( - id: string, - maxCacheAgeInSecs?: number - ): Promise - static async DownloadObjectAsync( - id: string, - maxCacheAgeInSecs?: number - ): Promise { - const splitted = id.split("/") - const type = splitted[0] - const idN = Number(splitted[1]) - if (idN < 0) { - return undefined - } - - const full = !id.startsWith("node") ? "/full" : "" - const url = `${OsmObject.backendURL}api/0.6/${id}${full}` - const rawData = await Utils.downloadJsonCached(url, (maxCacheAgeInSecs ?? 10) * 1000) - if (rawData === undefined) { - return undefined - } - // A full query might contain more then just the requested object (e.g. nodes that are part of a way, where we only want the way) - const parsed = OsmObject.ParseObjects(rawData.elements) - // Lets fetch the object we need - for (const osmObject of parsed) { - if (osmObject.type !== type) { - continue - } - if (osmObject.id !== idN) { - continue - } - // Found the one! - return osmObject - } - throw "PANIC: requested object is not part of the response" - } - - /** - * Downloads the ways that are using this node. - * Beware: their geometry will be incomplete! - */ - public static DownloadReferencingWays(id: string): Promise { - return Utils.downloadJsonCached( - `${OsmObject.backendURL}api/0.6/${id}/ways`, - 60 * 1000 - ).then((data) => { - return data.elements.map((wayInfo) => { - const way = new OsmWay(wayInfo.id) - way.LoadData(wayInfo) - return way - }) - }) - } - - /** - * Downloads the relations that are using this feature. - * Beware: their geometry will be incomplete! - */ - public static async DownloadReferencingRelations(id: string): Promise { - const data = await Utils.downloadJsonCached( - `${OsmObject.backendURL}api/0.6/${id}/relations`, - 60 * 1000 - ) - return data.elements.map((wayInfo) => { - const rel = new OsmRelation(wayInfo.id) - rel.LoadData(wayInfo) - rel.SaveExtraData(wayInfo, undefined) - return rel - }) - } - - public static DownloadHistory(id: NodeId): UIEventSource - public static DownloadHistory(id: WayId): UIEventSource - public static DownloadHistory(id: RelationId): UIEventSource - - public static DownloadHistory(id: OsmId): UIEventSource - public static DownloadHistory(id: string): UIEventSource { - if (OsmObject.historyCache.has(id)) { - return OsmObject.historyCache.get(id) - } - const splitted = id.split("/") - const type = splitted[0] - const idN = Number(splitted[1]) - const src = new UIEventSource([]) - OsmObject.historyCache.set(id, src) - Utils.downloadJsonCached( - `${OsmObject.backendURL}api/0.6/${type}/${idN}/history`, - 10 * 60 * 1000 - ).then((data) => { - const elements: any[] = data.elements - const osmObjects: OsmObject[] = [] - for (const element of elements) { - let osmObject: OsmObject = null - element.nodes = [] - switch (type) { - case "node": - osmObject = new OsmNode(idN) - break - case "way": - osmObject = new OsmWay(idN) - break - case "relation": - osmObject = new OsmRelation(idN) - break - } - osmObject?.LoadData(element) - osmObject?.SaveExtraData(element, []) - osmObjects.push(osmObject) - } - src.setData(osmObjects) - }) - return src - } - - // bounds should be: [[maxlat, minlon], [minlat, maxlon]] (same as Utils.tile_bounds) - public static async LoadArea(bbox: BBox): Promise { - const url = `${OsmObject.backendURL}api/0.6/map.json?bbox=${bbox.minLon},${bbox.minLat},${bbox.maxLon},${bbox.maxLat}` - const data = await Utils.downloadJson(url) - const elements: any[] = data.elements - return OsmObject.ParseObjects(elements) - } - public static ParseObjects(elements: any[]): OsmObject[] { const objects: OsmObject[] = [] const allNodes: Map = new Map() @@ -357,12 +169,16 @@ export abstract class OsmObject { return 'version="' + this.version + '"' } - private LoadData(element: any): void { - this.tags = element.tags ?? this.tags - this.version = element.version - this.timestamp = element.timestamp + protected LoadData(element: any): void { + if (element === undefined) { + return + } + this.tags = element?.tags ?? this.tags const tgs = this.tags - if (element.tags === undefined) { + tgs["id"] = (this.type + "/" + this.id) + this.version = element?.version + this.timestamp = element?.timestamp + if (element?.tags === undefined) { // Simple node which is part of a way - not important return } @@ -371,7 +187,6 @@ export abstract class OsmObject { tgs["_last_edit:changeset"] = element.changeset tgs["_last_edit:timestamp"] = element.timestamp tgs["_version_number"] = element.version - tgs["id"] = (this.type + "/" + this.id) } } @@ -379,8 +194,9 @@ export class OsmNode extends OsmObject { lat: number lon: number - constructor(id: number) { + constructor(id: number, extraData?) { super("node", id) + this.LoadData(extraData) } ChangesetXML(changesetId: string): string { @@ -431,8 +247,9 @@ export class OsmWay extends OsmObject { lat: number lon: number - constructor(id: number) { + constructor(id: number, wayInfo?) { super("way", id) + this.LoadData(wayInfo) } centerpoint(): [number, number] { @@ -535,8 +352,9 @@ export class OsmRelation extends OsmObject { private geojson = undefined - constructor(id: number) { + constructor(id: number, extraInfo?: any) { super("relation", id) + this.LoadData(extraInfo) } centerpoint(): [number, number] { diff --git a/Logic/Osm/OsmObjectDownloader.ts b/Logic/Osm/OsmObjectDownloader.ts new file mode 100644 index 0000000000..3ae7d214c0 --- /dev/null +++ b/Logic/Osm/OsmObjectDownloader.ts @@ -0,0 +1,152 @@ +import { Utils } from "../../Utils" +import { OsmNode, OsmObject, OsmRelation, OsmWay } from "./OsmObject" +import { NodeId, OsmId, RelationId, WayId } from "../../Models/OsmFeature" +import { Store, UIEventSource } from "../UIEventSource" +import { ChangeDescription } from "./Actions/ChangeDescription" + +/** + * The OSM-Object downloader downloads the latest version of the object, but applies 'pendingchanges' to them, + * so that we always have a consistent view + */ +export default class OsmObjectDownloader { + private readonly _changes?: { + readonly pendingChanges: UIEventSource + readonly isUploading: Store + } + private readonly backend: string + private historyCache = new Map>() + + constructor( + backend: string = "https://openstreetmap.org", + changes?: { + readonly pendingChanges: UIEventSource + readonly isUploading: Store + } + ) { + this._changes = changes + if (!backend.endsWith("/")) { + backend += "/" + } + if (!backend.startsWith("http")) { + throw "Backend URL must begin with http" + } + this.backend = backend + } + + async DownloadObjectAsync(id: NodeId, maxCacheAgeInSecs?: number): Promise + async DownloadObjectAsync(id: WayId, maxCacheAgeInSecs?: number): Promise + async DownloadObjectAsync( + id: RelationId, + maxCacheAgeInSecs?: number + ): Promise + async DownloadObjectAsync(id: OsmId, maxCacheAgeInSecs?: number): Promise + async DownloadObjectAsync( + id: string, + maxCacheAgeInSecs?: number + ): Promise + async DownloadObjectAsync( + id: string, + maxCacheAgeInSecs?: number + ): Promise { + const splitted = id.split("/") + const type = splitted[0] + const idN = Number(splitted[1]) + if (idN < 0) { + throw "Invalid request: cannot download OsmObject " + id + ", it has a negative id" + } + + const full = !id.startsWith("node") ? "/full" : "" + const url = `${this.backend}api/0.6/${id}${full}` + const rawData = await Utils.downloadJsonCachedAdvanced( + url, + (maxCacheAgeInSecs ?? 10) * 1000 + ) + if (rawData["error"] !== undefined && rawData["statuscode"] === 410) { + return "deleted" + } + // A full query might contain more then just the requested object (e.g. nodes that are part of a way, where we only want the way) + const parsed = OsmObject.ParseObjects(rawData["content"].elements) + // Lets fetch the object we need + for (const osmObject of parsed) { + if (osmObject.type !== type) { + continue + } + if (osmObject.id !== idN) { + continue + } + // Found the one! + return osmObject + } + throw "PANIC: requested object is not part of the response" + } + + public DownloadHistory(id: NodeId): UIEventSource + + public DownloadHistory(id: WayId): UIEventSource + + public DownloadHistory(id: RelationId): UIEventSource + + public DownloadHistory(id: OsmId): UIEventSource + + public DownloadHistory(id: string): UIEventSource { + if (this.historyCache.has(id)) { + return this.historyCache.get(id) + } + const splitted = id.split("/") + const type = splitted[0] + const idN = Number(splitted[1]) + const src = new UIEventSource([]) + this.historyCache.set(id, src) + Utils.downloadJsonCached( + `${this.backend}api/0.6/${type}/${idN}/history`, + 10 * 60 * 1000 + ).then((data) => { + const elements: any[] = data.elements + const osmObjects: OsmObject[] = [] + for (const element of elements) { + let osmObject: OsmObject = null + element.nodes = [] + switch (type) { + case "node": + osmObject = new OsmNode(idN, element) + break + case "way": + osmObject = new OsmWay(idN, element) + break + case "relation": + osmObject = new OsmRelation(idN, element) + break + } + osmObject?.SaveExtraData(element, []) + osmObjects.push(osmObject) + } + src.setData(osmObjects) + }) + return src + } + + /** + * Downloads the ways that are using this node. + * Beware: their geometry will be incomplete! + */ + public async DownloadReferencingWays(id: string): Promise { + const data = await Utils.downloadJsonCached(`${this.backend}api/0.6/${id}/ways`, 60 * 1000) + return data.elements.map((wayInfo) => new OsmWay(wayInfo.id, wayInfo)) + } + + /** + * Downloads the relations that are using this feature. + * Beware: their geometry will be incomplete! + */ + public async DownloadReferencingRelations(id: string): Promise { + const data = await Utils.downloadJsonCached( + `${this.backend}api/0.6/${id}/relations`, + 60 * 1000 + ) + return data.elements.map((wayInfo) => { + const rel = new OsmRelation(wayInfo.id, wayInfo) + rel.SaveExtraData(wayInfo, undefined) + return rel + }) + } +} diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index 34e36d7cb2..a71201e474 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -14,12 +14,14 @@ import { OsmObject } from "./Osm/OsmObject" import { OsmTags } from "../Models/OsmFeature" import { UIEventSource } from "./UIEventSource" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" +import OsmObjectDownloader from "./Osm/OsmObjectDownloader" /** * All elements that are needed to perform metatagging */ export interface MetataggingState { layout: LayoutConfig + osmObjectDownloader: OsmObjectDownloader } export abstract class SimpleMetaTagger { @@ -97,7 +99,7 @@ export class ReferencingWaysMetaTagger extends SimpleMetaTagger { } Utils.AddLazyPropertyAsync(feature.properties, "_referencing_ways", async () => { - const referencingWays = await OsmObject.DownloadReferencingWays(id) + const referencingWays = await state.osmObjectDownloader.DownloadReferencingWays(id) const wayIds = referencingWays.map((w) => "way/" + w.id) wayIds.sort() return wayIds.join(";") diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 5850f13ec8..3ac0d7a2c0 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -42,6 +42,7 @@ import { MenuState } from "./MenuState" import MetaTagging from "../Logic/MetaTagging" import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator" import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource" +import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; /** * @@ -64,6 +65,7 @@ export default class ThemeViewState implements SpecialVisualizationState { readonly osmConnection: OsmConnection readonly selectedElement: UIEventSource readonly mapProperties: MapProperties & ExportableMap + readonly osmObjectDownloader: OsmObjectDownloader readonly dataIsLoading: Store readonly guistate: MenuState @@ -213,6 +215,8 @@ export default class ThemeViewState implements SpecialVisualizationState { this.layout )) + this.osmObjectDownloader = new OsmObjectDownloader(this.osmConnection.Backend(), this.changes) + this.initActors() this.drawSpecialLayers(lastClick) this.initHotkeys() diff --git a/UI/BigComponents/ActionButtons.ts b/UI/BigComponents/ActionButtons.ts index 8cd9fbccd1..e001ae7aea 100644 --- a/UI/BigComponents/ActionButtons.ts +++ b/UI/BigComponents/ActionButtons.ts @@ -1,9 +1,5 @@ import Combine from "../Base/Combine" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { Store } from "../../Logic/UIEventSource" -import { BBox } from "../../Logic/BBox" -import Loc from "../../Models/Loc" -import { OsmConnection } from "../../Logic/Osm/OsmConnection" import Translations from "../i18n/Translations" import { SubtleButton } from "../Base/SubtleButton" import Svg from "../../Svg" @@ -11,7 +7,7 @@ import { Utils } from "../../Utils" import { MapillaryLink } from "./MapillaryLink" import { OpenIdEditor, OpenJosm } from "./CopyrightPanel" import Toggle from "../Input/Toggle" -import { DefaultGuiState } from "../DefaultGuiState" +import { SpecialVisualizationState } from "../SpecialVisualization" export class BackToThemeOverview extends Toggle { constructor( @@ -35,14 +31,7 @@ export class BackToThemeOverview extends Toggle { } export class ActionButtons extends Combine { - constructor(state: { - readonly layoutToUse: LayoutConfig - readonly currentBounds: Store - readonly locationControl: Store - readonly osmConnection: OsmConnection - readonly featureSwitchMoreQuests: Store - readonly defaultGuiState: DefaultGuiState - }) { + constructor(state:SpecialVisualizationState) { const imgSize = "h-6 w-6" const iconStyle = "height: 1.5rem; width: 1.5rem" const t = Translations.t.general.attribution @@ -76,7 +65,7 @@ export class ActionButtons extends Combine { }), new OpenIdEditor(state, iconStyle), new MapillaryLink(state, iconStyle), - new OpenJosm(state, iconStyle).SetClass("hidden-on-mobile"), + new OpenJosm(state.osmConnection,state.mapProperties.bounds, iconStyle).SetClass("hidden-on-mobile"), ]) this.SetClass("block w-full link-no-underline") } diff --git a/UI/Input/LocationInput.ts b/UI/Input/LocationInput.ts deleted file mode 100644 index 6c25cd00ea..0000000000 --- a/UI/Input/LocationInput.ts +++ /dev/null @@ -1,333 +0,0 @@ -import { ReadonlyInputElement } from "./InputElement" -import Loc from "../../Models/Loc" -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import Combine from "../Base/Combine" -import Svg from "../../Svg" -import { GeoOperations } from "../../Logic/GeoOperations" -import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { BBox } from "../../Logic/BBox" -import { FixedUiElement } from "../Base/FixedUiElement" -import BaseUIElement from "../BaseUIElement" -import matchpoint from "../../assets/layers/matchpoint/matchpoint.json" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import FilteredLayer from "../../Models/FilteredLayer" -import { RelationId, WayId } from "../../Models/OsmFeature" -import { Feature, LineString, Polygon } from "geojson" -import { OsmObject, OsmWay } from "../../Logic/Osm/OsmObject" - -export default class LocationInput - extends BaseUIElement - implements ReadonlyInputElement, MinimapObj -{ - private static readonly matchLayer = new LayerConfig( - matchpoint, - "LocationInput.matchpoint", - true - ) - - public readonly snappedOnto: UIEventSource = - new UIEventSource(undefined) - public readonly _matching_layer: LayerConfig - public readonly leafletMap: UIEventSource - public readonly bounds - public readonly location - private readonly _centerLocation: UIEventSource - private readonly mapBackground: UIEventSource - /** - * The features to which the input should be snapped - * @private - */ - private readonly _snapTo: Store< - (Feature & { properties: { id: WayId } })[] - > - /** - * The features to which the input should be snapped without cleanup of relations and memberships - * Used for rendering - * @private - */ - private readonly _snapToRaw: Store - private readonly _value: Store - private readonly _snappedPoint: Store - private readonly _maxSnapDistance: number - private readonly _snappedPointTags: any - private readonly _bounds: UIEventSource - private readonly map: BaseUIElement & MinimapObj - private readonly clickLocation: UIEventSource - private readonly _minZoom: number - private readonly _state: { - readonly filteredLayers: Store - readonly backgroundLayer: UIEventSource - readonly layoutToUse: LayoutConfig - readonly selectedElement: UIEventSource - readonly allElements: ElementStorage - } - - /** - * Given a list of geojson-features, will prepare these features to be snappable: - * - points are removed - * - LineStrings are passed as-is - * - Multipolygons are decomposed into their member ways by downloading them - * - * @private - */ - private static async prepareSnapOnto( - features: Feature[] - ): Promise<(Feature & { properties: { id: WayId } })[]> { - const linesAndPolygon: Feature[] = ( - features.filter((f) => f.geometry.type !== "Point") - ) - // Clean the features: multipolygons are split into their it's members - const linestrings: (Feature & { properties: { id: WayId } })[] = [] - for (const feature of linesAndPolygon) { - if (feature.properties.id.startsWith("way")) { - // A normal way - we continue - linestrings.push(feature) - continue - } - - // We have a multipolygon, thus: a relation - // Download the members - const relation = await OsmObject.DownloadObjectAsync( - feature.properties.id, - 60 * 60 - ) - const members: OsmWay[] = await Promise.all( - relation.members - .filter((m) => m.type === "way") - .map((m) => OsmObject.DownloadObjectAsync(("way/" + m.ref), 60 * 60)) - ) - linestrings.push(...members.map((m) => m.asGeoJson())) - } - return linestrings - } - - constructor(options?: { - minZoom?: number - mapBackground?: UIEventSource - snapTo?: UIEventSource - renderLayerForSnappedPoint?: LayerConfig - maxSnapDistance?: number - snappedPointTags?: any - requiresSnapping?: boolean - centerLocation?: UIEventSource - bounds?: UIEventSource - state?: { - readonly filteredLayers: Store - readonly backgroundLayer: UIEventSource - readonly layoutToUse: LayoutConfig - readonly selectedElement: UIEventSource - readonly allElements: ElementStorage - } - }) { - super() - this._snapToRaw = options?.snapTo?.map((feats) => - feats.filter((f) => f.feature.geometry.type !== "Point") - ) - this._snapTo = options?.snapTo - ?.bind((features) => - UIEventSource.FromPromise( - LocationInput.prepareSnapOnto(features.map((f) => f.feature)) - ) - ) - ?.map((f) => f ?? []) - this._maxSnapDistance = options?.maxSnapDistance - this._centerLocation = - options?.centerLocation ?? - new UIEventSource({ - lat: 0, - lon: 0, - zoom: 0, - }) - this._snappedPointTags = options?.snappedPointTags - this._bounds = options?.bounds - this._minZoom = options?.minZoom - this._state = options?.state - const self = this - if (this._snapTo === undefined) { - this._value = this._centerLocation - } else { - this._matching_layer = options?.renderLayerForSnappedPoint ?? LocationInput.matchLayer - - // Calculate the location of the point based by snapping it onto a way - // As a side-effect, the actual snapped-onto way (if any) is saved into 'snappedOnto' - this._snappedPoint = this._centerLocation.map( - (loc) => { - if (loc === undefined) { - return undefined - } - - // We reproject the location onto every 'snap-to-feature' and select the closest - - let min = undefined - let matchedWay: Feature & { properties: { id: WayId } } = - undefined - for (const feature of self._snapTo.data ?? []) { - try { - const nearestPointOnLine = GeoOperations.nearestPoint(feature, [ - loc.lon, - loc.lat, - ]) - if (min === undefined) { - min = { ...nearestPointOnLine } - matchedWay = feature - continue - } - - if (min.properties.dist > nearestPointOnLine.properties.dist) { - min = { ...nearestPointOnLine } - matchedWay = feature - } - } catch (e) { - console.log( - "Snapping to a nearest point failed for ", - feature, - "due to ", - e - ) - } - } - - if (min === undefined || min.properties.dist * 1000 > self._maxSnapDistance) { - if (options?.requiresSnapping) { - return undefined - } else { - // No match found - the original coordinates are returned as is - return { - type: "Feature", - properties: options?.snappedPointTags ?? min.properties, - geometry: { type: "Point", coordinates: [loc.lon, loc.lat] }, - } - } - } - min.properties = options?.snappedPointTags ?? min.properties - min.properties = { - ...min.properties, - _referencing_ways: JSON.stringify([matchedWay.properties.id]), - } - self.snappedOnto.setData(matchedWay) - return min - }, - [this._snapTo] - ) - - this._value = this._snappedPoint.map((f) => { - const [lon, lat] = f.geometry.coordinates - return { - lon: lon, - lat: lat, - zoom: undefined, - } - }) - } - this.mapBackground = - options?.mapBackground ?? - this._state?.backgroundLayer ?? - new UIEventSource(AvailableBaseLayers.osmCarto) - this.SetClass("block h-full") - - this.clickLocation = new UIEventSource(undefined) - this.map = Minimap.createMiniMap({ - location: this._centerLocation, - background: this.mapBackground, - attribution: this.mapBackground !== this._state?.backgroundLayer, - lastClickLocation: this.clickLocation, - bounds: this._bounds, - addLayerControl: true, - }) - this.leafletMap = this.map.leafletMap - this.location = this.map.location - } - - GetValue(): Store { - return this._value - } - - IsValid(t: Loc): boolean { - return t !== undefined - } - - installBounds(factor: number | BBox, showRange?: boolean): void { - this.map.installBounds(factor, showRange) - } - - protected InnerConstructElement(): HTMLElement { - try { - const self = this - const hasMoved = new UIEventSource(false) - const startLocation = { ...this._centerLocation.data } - this._centerLocation.addCallbackD((newLocation) => { - const f = 100000 - const diff = - Math.abs(newLocation.lon * f - startLocation.lon * f) + - Math.abs(newLocation.lat * f - startLocation.lat * f) - if (diff < 1) { - return - } - hasMoved.setData(true) - return true - }) - this.clickLocation.addCallbackAndRunD((location) => - this._centerLocation.setData(location) - ) - if (this._snapToRaw !== undefined) { - // Show the lines to snap to - new ShowDataMultiLayer({ - features: new StaticFeatureSource(this._snapToRaw), - zoomToFeatures: false, - leafletMap: this.map.leafletMap, - layers: this._state.filteredLayers, - }) - // Show the central point - const matchPoint = this._snappedPoint.map((loc) => { - if (loc === undefined) { - return [] - } - return [loc] - }) - - // The 'matchlayer' is the layer which shows the target location - new ShowDataLayer({ - features: new StaticFeatureSource(matchPoint), - zoomToFeatures: false, - leafletMap: this.map.leafletMap, - layerToShow: this._matching_layer, - state: this._state, - selectedElement: this._state.selectedElement, - }) - } - this.mapBackground.map( - (layer) => { - const leaflet = this.map.leafletMap.data - if (leaflet === undefined || layer === undefined) { - return - } - - leaflet.setMaxZoom(layer.max_zoom) - leaflet.setMinZoom(self._minZoom ?? layer.max_zoom - 2) - leaflet.setZoom(layer.max_zoom - 1) - }, - [this.map.leafletMap] - ) - - return new Combine([ - new Combine([ - Svg.move_arrows_ui() - .SetClass("block relative pointer-events-none") - .SetStyle("left: -2.5rem; top: -2.5rem; width: 5rem; height: 5rem"), - ]) - .SetClass("block w-0 h-0 z-10 relative") - .SetStyle( - "background: rgba(255, 128, 128, 0.21); left: 50%; top: 50%; opacity: 0.5" - ), - - this.map.SetClass("z-0 relative block w-full h-full bg-gray-100"), - ]).ConstructElement() - } catch (e) { - console.error("Could not generate LocationInputElement:", e) - return new FixedUiElement("Constructing a locationInput failed due to" + e) - .SetClass("alert") - .ConstructElement() - } - } -} diff --git a/UI/Popup/AddNewPoint/AddNewPoint.svelte b/UI/Popup/AddNewPoint/AddNewPoint.svelte index fef8dcf20b..9b60da791d 100644 --- a/UI/Popup/AddNewPoint/AddNewPoint.svelte +++ b/UI/Popup/AddNewPoint/AddNewPoint.svelte @@ -21,10 +21,9 @@ import LoginButton from "../../Base/LoginButton.svelte"; import NewPointLocationInput from "../../BigComponents/NewPointLocationInput.svelte"; import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction"; - import { OsmObject } from "../../../Logic/Osm/OsmObject"; + import { OsmWay } from "../../../Logic/Osm/OsmObject"; import { Tag } from "../../../Logic/Tags/Tag"; import type { WayId } from "../../../Models/OsmFeature"; - import { TagUtils } from "../../../Logic/Tags/TagUtils"; import Loading from "../../Base/Loading.svelte"; export let coordinate: { lon: number, lat: number }; @@ -75,12 +74,19 @@ const tags: Tag[] = selectedPreset.preset.tags; console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags); - const snapToWay = snapTo === undefined ? undefined : await OsmObject.DownloadObjectAsync(snapTo, 0); + let snapToWay: undefined | OsmWay = undefined + if(snapTo !== undefined){ + const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0); + if(downloaded !== "deleted"){ + snapToWay = downloaded + } + } - const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, { + const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, + { theme: state.layout?.id ?? "unkown", changeType: "create", - snapOnto: snapToWay + snapOnto: snapToWay }); await state.changes.applyAction(newElementAction); // The 'changes' should have created a new point, which added this into the 'featureProperties' diff --git a/UI/Popup/DeleteWizard.ts b/UI/Popup/DeleteWizard.ts index 2cd325c6ff..3ef4bb6394 100644 --- a/UI/Popup/DeleteWizard.ts +++ b/UI/Popup/DeleteWizard.ts @@ -11,7 +11,6 @@ import { Translation } from "../i18n/Translation" import BaseUIElement from "../BaseUIElement" import Constants from "../../Models/Constants" import DeleteConfig from "../../Models/ThemeConfig/DeleteConfig" -import { OsmObject } from "../../Logic/Osm/OsmObject" import { OsmConnection } from "../../Logic/Osm/OsmConnection" import OsmChangeAction from "../../Logic/Osm/Actions/OsmChangeAction" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" @@ -20,12 +19,12 @@ import { RadioButton } from "../Input/RadioButton" import { FixedInputElement } from "../Input/FixedInputElement" import Title from "../Base/Title" import { SubstitutedTranslation } from "../SubstitutedTranslation" -import TagRenderingQuestion from "./TagRenderingQuestion" import { OsmId, OsmTags } from "../../Models/OsmFeature" import { LoginToggle } from "./LoginButton" import { SpecialVisualizationState } from "../SpecialVisualization" -import SvelteUIElement from "../Base/SvelteUIElement"; -import TagHint from "./TagHint.svelte"; +import SvelteUIElement from "../Base/SvelteUIElement" +import TagHint from "./TagHint.svelte" +import OsmObjectDownloader from "../../Logic/Osm/OsmObjectDownloader" export default class DeleteWizard extends Toggle { /** @@ -53,6 +52,7 @@ export default class DeleteWizard extends Toggle { const deleteAbility = new DeleteabilityChecker( id, state.osmConnection, + state.osmObjectDownloader, options.neededChangesets ) @@ -227,7 +227,10 @@ export default class DeleteWizard extends Toggle { // This is a retagging, not a deletion of any kind return new Combine([ t.explanations.retagNoOtherThemes, - new SvelteUIElement(TagHint, {osmConnection: state.osmConnection, tags: retag}) + new SvelteUIElement(TagHint, { + osmConnection: state.osmConnection, + tags: retag, + }), ]) } @@ -285,11 +288,18 @@ export default class DeleteWizard extends Toggle { class DeleteabilityChecker { public readonly canBeDeleted: UIEventSource<{ canBeDeleted?: boolean; reason: Translation }> + private readonly objectDownloader: OsmObjectDownloader private readonly _id: OsmId private readonly _allowDeletionAtChangesetCount: number private readonly _osmConnection: OsmConnection - constructor(id: OsmId, osmConnection: OsmConnection, allowDeletionAtChangesetCount?: number) { + constructor( + id: OsmId, + osmConnection: OsmConnection, + objectDownloader: OsmObjectDownloader, + allowDeletionAtChangesetCount?: number + ) { + this.objectDownloader = objectDownloader this._id = id this._osmConnection = osmConnection this._allowDeletionAtChangesetCount = allowDeletionAtChangesetCount ?? Number.MAX_VALUE @@ -366,11 +376,13 @@ class DeleteabilityChecker { if (allByMyself.data === null && useTheInternet) { // We kickoff the download here as it hasn't yet been downloaded. Note that this is mapped onto 'all by myself' above - const hist = OsmObject.DownloadHistory(id).map((versions) => - versions.map((version) => - Number(version.tags["_last_edit:contributor:uid"]) + const hist = this.objectDownloader + .DownloadHistory(id) + .map((versions) => + versions.map((version) => + Number(version.tags["_last_edit:contributor:uid"]) + ) ) - ) hist.addCallbackAndRunD((hist) => previousEditors.setData(hist)) } @@ -406,11 +418,11 @@ class DeleteabilityChecker { } // All right! We have arrived at a point that we should query OSM again to check that the point isn't a part of ways or relations - OsmObject.DownloadReferencingRelations(id).then((rels) => { + this.objectDownloader.DownloadReferencingRelations(id).then((rels) => { hasRelations.setData(rels.length > 0) }) - OsmObject.DownloadReferencingWays(id).then((ways) => { + this.objectDownloader.DownloadReferencingWays(id).then((ways) => { hasWays.setData(ways.length > 0) }) return true // unregister to only run once diff --git a/UI/Popup/MoveWizard.ts b/UI/Popup/MoveWizard.ts index 0fba077ced..294529d7fe 100644 --- a/UI/Popup/MoveWizard.ts +++ b/UI/Popup/MoveWizard.ts @@ -233,13 +233,13 @@ export default class MoveWizard extends Toggle { } else if (id.startsWith("relation")) { moveDisallowedReason.setData(t.isRelation) } else if (id.indexOf("-") < 0) { - OsmObject.DownloadReferencingWays(id).then((referencing) => { + state.osmObjectDownloader.DownloadReferencingWays(id).then((referencing) => { if (referencing.length > 0) { console.log("Got a referencing way, move not allowed") moveDisallowedReason.setData(t.partOfAWay) } }) - OsmObject.DownloadReferencingRelations(id).then((partOf) => { + state.osmObjectDownloader.DownloadReferencingRelations(id).then((partOf) => { if (partOf.length > 0) { moveDisallowedReason.setData(t.partOfRelation) } diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index bf3ab9197b..9abed2cbb0 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -12,13 +12,13 @@ import { VariableUiElement } from "../Base/VariableUIElement" import { LoginToggle } from "./LoginButton" import SvelteUIElement from "../Base/SvelteUIElement" import WaySplitMap from "../BigComponents/WaySplitMap.svelte" -import { OsmObject } from "../../Logic/Osm/OsmObject" import { Feature, Point } from "geojson" import { WayId } from "../../Models/OsmFeature" import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { Changes } from "../../Logic/Osm/Changes" import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" +import OsmObjectDownloader from "../../Logic/Osm/OsmObjectDownloader" export default class SplitRoadWizard extends Combine { public dialogIsOpened: UIEventSource @@ -34,6 +34,7 @@ export default class SplitRoadWizard extends Combine { state: { layout?: LayoutConfig osmConnection?: OsmConnection + osmObjectDownloader?: OsmObjectDownloader changes?: Changes indexedFeatures?: IndexedFeatureSource selectedElement?: UIEventSource @@ -52,7 +53,15 @@ export default class SplitRoadWizard extends Combine { const leafletMap = new UIEventSource(undefined) function initMap() { - SplitRoadWizard.setupMapComponent(id, splitPoints).then((mapComponent) => + ;(async function ( + id: WayId, + splitPoints: UIEventSource + ): Promise { + return new SvelteUIElement(WaySplitMap, { + osmWay: await state.osmObjectDownloader.DownloadObjectAsync(id), + splitPoints, + }) + })(id, splitPoints).then((mapComponent) => leafletMap.setData(mapComponent.SetClass("w-full h-80")) ) } @@ -132,15 +141,4 @@ export default class SplitRoadWizard extends Combine { self.ScrollIntoView() }) } - - private static async setupMapComponent( - id: WayId, - splitPoints: UIEventSource - ): Promise { - const osmWay = await OsmObject.DownloadObjectAsync(id) - return new SvelteUIElement(WaySplitMap, { - osmWay, - splitPoints, - }) - } } diff --git a/UI/SpecialVisualization.ts b/UI/SpecialVisualization.ts index c30da739ca..9e64d16ace 100644 --- a/UI/SpecialVisualization.ts +++ b/UI/SpecialVisualization.ts @@ -13,6 +13,7 @@ import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexe import LayerConfig from "../Models/ThemeConfig/LayerConfig"; import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; import { MenuState } from "../Models/MenuState"; +import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; /** * The state needed to render a special Visualisation. @@ -39,6 +40,7 @@ export interface SpecialVisualizationState { readonly featureSwitchUserbadge: Store readonly featureSwitchIsTesting: Store readonly changes: Changes + readonly osmObjectDownloader: OsmObjectDownloader /** * State of the main map */ diff --git a/scripts/CycleHighwayFix.ts b/scripts/CycleHighwayFix.ts index 637e13c287..246f7064f3 100644 --- a/scripts/CycleHighwayFix.ts +++ b/scripts/CycleHighwayFix.ts @@ -1,6 +1,7 @@ import ScriptUtils from "./ScriptUtils" import { appendFileSync, readFileSync, writeFileSync } from "fs" import { OsmObject } from "../Logic/Osm/OsmObject" +import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; ScriptUtils.fixUtils() @@ -17,7 +18,7 @@ const ids = JSON.parse(readFileSync("export.geojson", "utf-8")).features.map( ) console.log(ids) ids.map((id) => - OsmObject.DownloadReferencingRelations(id).then((relations) => { + new OsmObjectDownloader().DownloadReferencingRelations(id).then((relations) => { console.log(relations) const changeparts = relations .filter( diff --git a/test/Logic/OSM/Actions/RelationSplitHandler.spec.ts b/test/Logic/OSM/Actions/RelationSplitHandler.spec.ts index 7392ffaa01..d73d729e7c 100644 --- a/test/Logic/OSM/Actions/RelationSplitHandler.spec.ts +++ b/test/Logic/OSM/Actions/RelationSplitHandler.spec.ts @@ -1,11 +1,14 @@ import { Utils } from "../../../../Utils" -import { OsmObject, OsmRelation } from "../../../../Logic/Osm/OsmObject" +import { OsmRelation } from "../../../../Logic/Osm/OsmObject" import { InPlaceReplacedmentRTSH, TurnRestrictionRSH, } from "../../../../Logic/Osm/Actions/RelationSplitHandler" import { Changes } from "../../../../Logic/Osm/Changes" import { describe, expect, it } from "vitest" +import OsmObjectDownloader from "../../../../Logic/Osm/OsmObjectDownloader" +import { ImmutableStore } from "../../../../Logic/UIEventSource" +import { OsmConnection } from "../../../../Logic/Osm/OsmConnection" describe("RelationSplitHandler", () => { Utils.injectJsonDownloadForTests("https://www.openstreetmap.org/api/0.6/node/1124134958/ways", { @@ -624,8 +627,9 @@ describe("RelationSplitHandler", () => { it("should split all cycling relation (split 295132739)", async () => { // Lets mimic a split action of https://www.openstreetmap.org/way/295132739 + const downloader = new OsmObjectDownloader() const relation: OsmRelation = ( - await OsmObject.DownloadObjectAsync("relation/9572808") + await downloader.DownloadObjectAsync("relation/9572808") ) const originalNodeIds = [ 5273988967, 170497153, 1507524582, 4524321710, 170497155, 170497157, 170497158, @@ -645,9 +649,13 @@ describe("RelationSplitHandler", () => { originalNodes: originalNodeIds, allWaysNodesInOrder: withSplit, }, - "no-theme" + "no-theme", + downloader ) - const changeDescription = await splitter.CreateChangeDescriptions(new Changes()) + const changeDescription = await splitter.CreateChangeDescriptions(new Changes({ + dryRun: new ImmutableStore(false), + osmConnection: new OsmConnection() + })) const allIds = changeDescription[0].changes["members"].map((m) => m.ref).join(",") const expected = "687866206,295132739,-1,690497698" // "didn't find the expected order of ids in the relation to test" @@ -655,8 +663,9 @@ describe("RelationSplitHandler", () => { }) it("should split turn restrictions (split of https://www.openstreetmap.org/way/143298912)", async () => { + const downloader = new OsmObjectDownloader() const relation: OsmRelation = ( - await OsmObject.DownloadObjectAsync("relation/4374576") + await downloader.DownloadObjectAsync("relation/4374576") ) const originalNodeIds = [ 1407529979, 1974988033, 3250129361, 1634435395, 8493044168, 875668688, 1634435396, @@ -695,9 +704,13 @@ describe("RelationSplitHandler", () => { originalNodes: originalNodeIds, allWaysNodesInOrder: withSplit, }, - "no-theme" + "no-theme", + downloader ) - const changeDescription = await splitter.CreateChangeDescriptions(new Changes()) + const changeDescription = await splitter.CreateChangeDescriptions(new Changes({ + dryRun: new ImmutableStore(false), + osmConnection: new OsmConnection() + })) const allIds = changeDescription[0].changes["members"] .map((m) => m.type + "/" + m.ref + "-->" + m.role) .join(",") @@ -713,9 +726,15 @@ describe("RelationSplitHandler", () => { originalNodes: originalNodeIds, allWaysNodesInOrder: withSplit, }, - "no-theme" + "no-theme", + downloader + ) + const changesReverse = await splitterReverse.CreateChangeDescriptions( + new Changes({ + dryRun: new ImmutableStore(false), + osmConnection: new OsmConnection(), + }) ) - const changesReverse = await splitterReverse.CreateChangeDescriptions(new Changes()) expect(changesReverse.length).toEqual(0) }) }) diff --git a/test/Logic/OSM/OsmObject.spec.ts b/test/Logic/OSM/OsmObject.spec.ts index b619d41d87..5cf3fcc3ff 100644 --- a/test/Logic/OSM/OsmObject.spec.ts +++ b/test/Logic/OSM/OsmObject.spec.ts @@ -3,6 +3,7 @@ import { Utils } from "../../../Utils" import ScriptUtils from "../../../scripts/ScriptUtils" import { readFileSync } from "fs" import { describe, expect, it } from "vitest" +import OsmObjectDownloader from "../../../Logic/Osm/OsmObjectDownloader" describe("OsmObject", () => { describe("download referencing ways", () => { @@ -79,7 +80,8 @@ describe("OsmObject", () => { ) it("should download referencing ways", async () => { - const ways = await OsmObject.DownloadReferencingWays("node/1124134958") + const downloader = new OsmObjectDownloader() + const ways = await downloader.DownloadReferencingWays("node/1124134958") expect(ways).toBeDefined() expect(ways).toHaveLength(4) }) @@ -90,7 +92,7 @@ describe("OsmObject", () => { "https://www.openstreetmap.org/api/0.6/relation/5759328/full", JSON.parse(readFileSync("./test/data/relation_5759328.json", { encoding: "utf-8" })) ) - const r = await OsmObject.DownloadObjectAsync("relation/5759328").then((x) => x) + const r = await new OsmObjectDownloader().DownloadObjectAsync("relation/5759328").then((x) => x) const geojson = r.asGeoJson() expect(geojson.geometry.type).toBe("MultiPolygon") }) From 4172af6a72a45ba7fe8a5bea5bcb6c39e02a4091 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 20 Apr 2023 17:42:07 +0200 Subject: [PATCH 050/257] Refactoring: fix delete indication, fix splitroad, fix addition of multiple new points snapped onto the same way (all will properly attach now) --- Logic/Actors/SelectedElementTagsUpdater.ts | 6 +- .../Actors/FeaturePropertiesStore.ts | 2 + .../Sources/FilteringFeatureSource.ts | 4 +- Logic/FeatureSource/Sources/LayoutSource.ts | 11 +- .../NewGeometryFromChangesFeatureSource.ts | 11 +- Logic/Osm/Actions/OsmChangeAction.ts | 4 +- Logic/Osm/OsmObjectDownloader.ts | 147 +++++++-- Logic/SimpleMetaTagger.ts | 22 ++ Logic/State/FeatureSwitchState.ts | 8 +- Logic/State/UserRelatedState.ts | 5 +- Logic/UIEventSource.ts | 5 +- Models/ThemeConfig/Conversion/PrepareLayer.ts | 7 + .../Json/TagRenderingConfigJson.ts | 6 + Models/ThemeConfig/TagRenderingConfig.ts | 7 +- UI/AllThemesGui.ts | 7 +- UI/Base/Hotkeys.ts | 13 +- UI/BigComponents/ActionButtons.ts | 72 ----- UI/BigComponents/AllDownloads.ts | 12 +- UI/BigComponents/SelectedElementView.svelte | 62 ++-- UI/BigComponents/ThemeIntroductionPanel.ts | 2 - UI/InputElement/Helpers/LocationInput.svelte | 2 +- UI/Map/MapLibreAdaptor.ts | 13 +- UI/Map/ShowDataLayer.ts | 3 - UI/Popup/AddNewPoint/AddNewPoint.svelte | 6 +- UI/Popup/FeatureInfoBox.ts | 60 +--- UI/Popup/SplitRoadWizard.ts | 7 +- UI/Popup/TagRendering/Questionbox.svelte | 1 - .../TagRendering/TagRenderingAnswer.svelte | 4 +- UI/ThemeViewGUI.svelte | 72 +++-- UI/i18n/Translations.ts | 3 +- Utils.ts | 10 + assets/layers/address/address.json | 21 +- assets/layers/advertising/advertising.json | 96 ++++-- assets/layers/artwork/artwork.json | 19 +- assets/layers/atm/atm.json | 82 +++-- assets/layers/bank/bank.json | 21 +- assets/layers/barrier/barrier.json | 3 +- assets/layers/bench/bench.json | 57 ++-- assets/layers/bench_at_pt/bench_at_pt.json | 9 +- .../layers/bicycle_rental/bicycle_rental.json | 6 +- assets/layers/bike_cafe/bike_cafe.json | 9 +- .../layers/bike_cleaning/bike_cleaning.json | 5 +- .../bike_repair_station.json | 6 +- assets/layers/bike_shop/bike_shop.json | 45 ++- .../bike_themed_object.json | 6 +- assets/layers/binocular/binocular.json | 5 +- assets/layers/birdhide/birdhide.json | 21 +- .../charging_station/charging_station.json | 95 ++++-- assets/layers/climbing/climbing.json | 33 +- assets/layers/clock/clock.json | 72 +++-- .../cycleways_and_roads.json | 3 +- .../layers/defibrillator/defibrillator.json | 13 +- assets/layers/dogpark/dogpark.json | 3 +- .../layers/drinking_water/drinking_water.json | 24 +- assets/layers/fire_station/fire_station.json | 8 +- .../layers/fitness_centre/fitness_centre.json | 3 +- assets/layers/food/food.json | 69 +++-- assets/layers/governments/governments.json | 3 +- assets/layers/hackerspace/hackerspace.json | 2 +- .../information_board/information_board.json | 7 +- .../layers/nature_reserve/nature_reserve.json | 54 ++-- .../observation_tower/observation_tower.json | 2 +- assets/layers/parking/parking.json | 3 +- assets/layers/picnic_table/picnic_table.json | 3 +- assets/layers/playground/playground.json | 63 ++-- assets/layers/postoffices/postoffices.json | 18 +- .../public_bookcase/public_bookcase.json | 3 +- assets/layers/recycling/recycling.json | 3 +- assets/layers/split_road/split_road.json | 2 +- assets/layers/sport_pitch/sport_pitch.json | 7 +- assets/layers/street_lamps/street_lamps.json | 6 +- assets/layers/toilet/toilet.json | 18 +- .../toilet_at_amenity/toilet_at_amenity.json | 27 +- .../layers/transit_routes/transit_routes.json | 6 +- .../layers/transit_stops/transit_stops.json | 15 +- assets/layers/tree_node/tree_node.json | 4 +- assets/tagRenderings/questions.json | 42 ++- assets/themes/cyclestreets/cyclestreets.json | 2 +- langs/ca.json | 3 - langs/cs.json | 2 - langs/da.json | 1 - langs/de.json | 3 - langs/en.json | 7 +- langs/es.json | 1 - langs/fr.json | 3 - langs/hu.json | 1 - langs/it.json | 1 - langs/ja.json | 1 - langs/layers/ca.json | 254 ++++++++-------- langs/layers/cs.json | 57 ++-- langs/layers/en.json | 3 + langs/layers/es.json | 287 +++++++++--------- langs/layers/fr.json | 118 +++---- langs/layers/nl.json | 3 + langs/layers/pt_BR.json | 2 +- langs/nb_NO.json | 1 - langs/nl.json | 3 - langs/pl.json | 1 - langs/pt.json | 1 - langs/pt_BR.json | 1 - langs/ru.json | 1 - langs/shared-questions/ca.json | 2 +- langs/shared-questions/es.json | 2 +- langs/themes/ca.json | 104 +------ langs/themes/cs.json | 172 +++-------- langs/themes/da.json | 2 +- langs/themes/de.json | 104 +------ langs/themes/en.json | 2 +- langs/themes/es.json | 2 +- langs/themes/fr.json | 2 +- langs/themes/hu.json | 2 +- langs/themes/it.json | 2 +- langs/themes/ja.json | 2 +- langs/themes/nb_NO.json | 2 +- langs/themes/nl.json | 2 +- langs/themes/pa_PK.json | 2 +- langs/zh_Hans.json | 1 - langs/zh_Hant.json | 1 - 118 files changed, 1422 insertions(+), 1357 deletions(-) delete mode 100644 UI/BigComponents/ActionButtons.ts diff --git a/Logic/Actors/SelectedElementTagsUpdater.ts b/Logic/Actors/SelectedElementTagsUpdater.ts index 8bfe511d82..135fbd6e44 100644 --- a/Logic/Actors/SelectedElementTagsUpdater.ts +++ b/Logic/Actors/SelectedElementTagsUpdater.ts @@ -74,12 +74,10 @@ export default class SelectedElementTagsUpdater { try { const osmObject = await state.osmObjectDownloader.DownloadObjectAsync(id) if (osmObject === "deleted") { - console.warn("The current selected element has been deleted upstream!") + console.debug("The current selected element has been deleted upstream!", id) const currentTagsSource = state.featureProperties.getStore(id) - if (currentTagsSource.data["_deleted"] === "yes") { - return - } currentTagsSource.data["_deleted"] = "yes" + currentTagsSource.addCallbackAndRun((tags) => console.trace("Tags are", tags)) currentTagsSource.ping() return } diff --git a/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts b/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts index 1708852d97..9cadb5df31 100644 --- a/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts +++ b/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts @@ -12,6 +12,7 @@ export default class FeaturePropertiesStore { this._source = source const self = this source.features.addCallbackAndRunD((features) => { + console.log("Re-indexing features") for (const feature of features) { const id = feature.properties.id if (id === undefined) { @@ -21,6 +22,7 @@ export default class FeaturePropertiesStore { const source = self._elements.get(id) if (source === undefined) { + console.log("Adding feature store for", id) self._elements.set(id, new UIEventSource(feature.properties)) continue } diff --git a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts index 2a2b52d760..8d1636bee2 100644 --- a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts @@ -1,7 +1,6 @@ import { Store, UIEventSource } from "../../UIEventSource" import FilteredLayer from "../../../Models/FilteredLayer" import { FeatureSource } from "../FeatureSource" -import { TagsFilter } from "../../Tags/TagsFilter" import { Feature } from "geojson" import { GlobalFilter } from "../../../Models/GlobalFilter" @@ -54,6 +53,7 @@ export default class FilteringFeatureSource implements FeatureSource { this.update() } + private update() { const self = this const layer = this._layer @@ -87,7 +87,7 @@ export default class FilteringFeatureSource implements FeatureSource { } } - // Something new has been found! + // Something new has been found (or something was deleted)! this.features.setData(newFeatures) } diff --git a/Logic/FeatureSource/Sources/LayoutSource.ts b/Logic/FeatureSource/Sources/LayoutSource.ts index e142a489a1..bfd7b86419 100644 --- a/Logic/FeatureSource/Sources/LayoutSource.ts +++ b/Logic/FeatureSource/Sources/LayoutSource.ts @@ -43,7 +43,15 @@ export default class LayoutSource extends FeatureSourceMerger { isActive: isDisplayed(l.id), }) ) - const overpassSource = LayoutSource.setupOverpass(osmLayers, bounds, zoom, featureSwitches) + + const overpassSource = LayoutSource.setupOverpass( + backend, + osmLayers, + bounds, + zoom, + featureSwitches + ) + const osmApiSource = LayoutSource.setupOsmApiSource( osmLayers, bounds, @@ -121,6 +129,7 @@ export default class LayoutSource extends FeatureSourceMerger { } private static setupOverpass( + backend: string, osmLayers: LayerConfig[], bounds: Store, zoom: Store, diff --git a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts index c3cc099d8a..a9b8841671 100644 --- a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts +++ b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts @@ -23,7 +23,7 @@ export class NewGeometryFromChangesFeatureSource implements WritableFeatureSourc const features = this.features.data const self = this const backend = changes.backend - changes.pendingChanges.stabilized(100).addCallbackAndRunD((changes) => { + changes.pendingChanges.addCallbackAndRunD((changes) => { if (changes.length === 0) { return } @@ -48,6 +48,7 @@ export class NewGeometryFromChangesFeatureSource implements WritableFeatureSourc continue } + console.log("Handling pending change") if (change.id > 0) { // This is an already existing object // In _most_ of the cases, this means that this _isn't_ a new object @@ -74,11 +75,9 @@ export class NewGeometryFromChangesFeatureSource implements WritableFeatureSourc self.features.ping() }) continue - } else if (change.id < 0 && change.changes === undefined) { - // The geometry is not described - not a new point - if (change.id < 0) { - console.error("WARNING: got a new point without geometry!") - } + } else if (change.changes === undefined) { + // The geometry is not described - not a new point or geometry change, but probably a tagchange to a newly created point + // Not something that should be handled here continue } diff --git a/Logic/Osm/Actions/OsmChangeAction.ts b/Logic/Osm/Actions/OsmChangeAction.ts index e07086c1c4..335d9a14cd 100644 --- a/Logic/Osm/Actions/OsmChangeAction.ts +++ b/Logic/Osm/Actions/OsmChangeAction.ts @@ -20,12 +20,12 @@ export default abstract class OsmChangeAction { this.mainObjectId = mainObjectId } - public Perform(changes: Changes) { + public async Perform(changes: Changes) { if (this.isUsed) { throw "This ChangeAction is already used" } this.isUsed = true - return this.CreateChangeDescriptions(changes) + return await this.CreateChangeDescriptions(changes) } protected abstract CreateChangeDescriptions(changes: Changes): Promise diff --git a/Logic/Osm/OsmObjectDownloader.ts b/Logic/Osm/OsmObjectDownloader.ts index 3ae7d214c0..6408f340ef 100644 --- a/Logic/Osm/OsmObjectDownloader.ts +++ b/Logic/Osm/OsmObjectDownloader.ts @@ -34,50 +34,40 @@ export default class OsmObjectDownloader { } async DownloadObjectAsync(id: NodeId, maxCacheAgeInSecs?: number): Promise + async DownloadObjectAsync(id: WayId, maxCacheAgeInSecs?: number): Promise + async DownloadObjectAsync( id: RelationId, maxCacheAgeInSecs?: number ): Promise + async DownloadObjectAsync(id: OsmId, maxCacheAgeInSecs?: number): Promise + async DownloadObjectAsync( id: string, maxCacheAgeInSecs?: number ): Promise - async DownloadObjectAsync( - id: string, - maxCacheAgeInSecs?: number - ): Promise { + + async DownloadObjectAsync(id: string, maxCacheAgeInSecs?: number) { + // Wait until uploading is done + if (this._changes) { + await this._changes.isUploading.AsPromise((o) => o === false) + } + const splitted = id.split("/") const type = splitted[0] const idN = Number(splitted[1]) + let obj: OsmObject | "deleted" if (idN < 0) { - throw "Invalid request: cannot download OsmObject " + id + ", it has a negative id" + obj = this.constructObject(<"node" | "way" | "relation">type, idN) + } else { + obj = await this.RawDownloadObjectAsync(type, idN, maxCacheAgeInSecs) } - - const full = !id.startsWith("node") ? "/full" : "" - const url = `${this.backend}api/0.6/${id}${full}` - const rawData = await Utils.downloadJsonCachedAdvanced( - url, - (maxCacheAgeInSecs ?? 10) * 1000 - ) - if (rawData["error"] !== undefined && rawData["statuscode"] === 410) { - return "deleted" + if (obj === "deleted") { + return obj } - // A full query might contain more then just the requested object (e.g. nodes that are part of a way, where we only want the way) - const parsed = OsmObject.ParseObjects(rawData["content"].elements) - // Lets fetch the object we need - for (const osmObject of parsed) { - if (osmObject.type !== type) { - continue - } - if (osmObject.id !== idN) { - continue - } - // Found the one! - return osmObject - } - throw "PANIC: requested object is not part of the response" + return await this.applyPendingChanges(obj) } public DownloadHistory(id: NodeId): UIEventSource @@ -149,4 +139,105 @@ export default class OsmObjectDownloader { return rel }) } + + private applyNodeChange(object: OsmNode, change: { lat: number; lon: number }) { + object.lat = change.lat + object.lon = change.lon + } + + private applyWayChange(object: OsmWay, change: { nodes: number[]; coordinates }) { + object.nodes = change.nodes + object.coordinates = change.coordinates.map(([lat, lon]) => [lon, lat]) + } + + private applyRelationChange( + object: OsmRelation, + change: { members: { type: "node" | "way" | "relation"; ref: number; role: string }[] } + ) { + object.members = change.members + } + + private async applyPendingChanges(object: OsmObject): Promise { + if (!this._changes) { + return object + } + const pendingChanges = this._changes.pendingChanges.data + for (const pendingChange of pendingChanges) { + if (object.id !== pendingChange.id || object.type !== pendingChange.type) { + continue + } + if (pendingChange.doDelete) { + return "deleted" + } + if (pendingChange.tags) { + for (const { k, v } of pendingChange.tags) { + if (v === undefined) { + delete object.tags[k] + } else { + object.tags[k] = v + } + } + } + + if (pendingChange.changes) { + switch (pendingChange.type) { + case "node": + this.applyNodeChange(object, pendingChange.changes) + break + case "way": + this.applyWayChange(object, pendingChange.changes) + break + case "relation": + this.applyRelationChange(object, pendingChange.changes) + break + } + } + } + return object + } + + /** + * Creates an empty object of the specified type with the specified id. + * We assume that the pending changes will be applied on them, filling in details such as coordinates, tags, ... + */ + private constructObject(type: "node" | "way" | "relation", id: number): OsmObject { + switch (type) { + case "node": + return new OsmNode(id) + case "way": + return new OsmWay(id) + case "relation": + return new OsmRelation(id) + } + } + + private async RawDownloadObjectAsync( + type: string, + idN: number, + maxCacheAgeInSecs?: number + ): Promise { + const full = type !== "node" ? "/full" : "" + const url = `${this.backend}api/0.6/${type}/${idN}${full}` + const rawData = await Utils.downloadJsonCachedAdvanced( + url, + (maxCacheAgeInSecs ?? 10) * 1000 + ) + if (rawData["error"] !== undefined && rawData["statuscode"] === 410) { + return "deleted" + } + // A full query might contain more then just the requested object (e.g. nodes that are part of a way, where we only want the way) + const parsed = OsmObject.ParseObjects(rawData["content"].elements) + // Lets fetch the object we need + for (const osmObject of parsed) { + if (osmObject.type !== type) { + continue + } + if (osmObject.id !== idN) { + continue + } + // Found the one! + return osmObject + } + throw "PANIC: requested object is not part of the response" + } } diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index a71201e474..7c177416d2 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -556,6 +556,27 @@ export default class SimpleMetaTaggers { return true } ) + + private static timeSinceLastEdit = new InlineMetaTagger( + { + keys: ["_last_edit:passed_time"], + doc: "Gives the number of seconds since the last edit. Note that this will _not_ update, but rather be the number of seconds elapsed at the moment this tag is read first", + isLazy: true, + includesDates: true, + }, + (feature, layer, tagsStore) => { + Utils.AddLazyProperty(feature.properties, "_last_edit:passed_time", () => { + const lastEditTimestamp = new Date( + feature.properties["_last_edit:timestamp"] + ).getTime() + const now: number = Date.now() + const millisElapsed = now - lastEditTimestamp + return "" + millisElapsed / 1000 + }) + return true + } + ) + public static metatags: SimpleMetaTagger[] = [ SimpleMetaTaggers.latlon, SimpleMetaTaggers.layerInfo, @@ -572,6 +593,7 @@ export default class SimpleMetaTaggers { SimpleMetaTaggers.geometryType, SimpleMetaTaggers.levels, SimpleMetaTaggers.referencingWays, + SimpleMetaTaggers.timeSinceLastEdit, ] /** diff --git a/Logic/State/FeatureSwitchState.ts b/Logic/State/FeatureSwitchState.ts index d012ef3de7..b786a06473 100644 --- a/Logic/State/FeatureSwitchState.ts +++ b/Logic/State/FeatureSwitchState.ts @@ -30,7 +30,6 @@ export default class FeatureSwitchState { public readonly featureSwitchFilter: UIEventSource public readonly featureSwitchEnableExport: UIEventSource public readonly featureSwitchFakeUser: UIEventSource - public readonly featureSwitchExportAsPdf: UIEventSource public readonly overpassUrl: UIEventSource public readonly overpassTimeout: UIEventSource public readonly overpassMaxZoom: UIEventSource @@ -125,14 +124,9 @@ export default class FeatureSwitchState { this.featureSwitchEnableExport = featSw( "fs-export", - (layoutToUse) => layoutToUse?.enableExportButton ?? false, + (layoutToUse) => layoutToUse?.enableExportButton ?? true, "Enable the export as GeoJSON and CSV button" ) - this.featureSwitchExportAsPdf = featSw( - "fs-pdf", - (layoutToUse) => layoutToUse?.enablePdfDownload ?? false, - "Enable the PDF download button" - ) this.featureSwitchApiURL = QueryParameters.GetQueryParameter( "backend", diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index a6879f4f80..4f0c4e391d 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -250,6 +250,7 @@ export default class UserRelatedState { const amendedPrefs = new UIEventSource>({ _theme: layout?.id, _backend: this.osmConnection.Backend(), + _applicationOpened: new Date().toISOString(), }) const osmConnection = this.osmConnection @@ -299,7 +300,6 @@ export default class UserRelatedState { "" + (total - untranslated_count) amendedPrefs.data["_translation_percentage"] = "" + Math.floor((100 * (total - untranslated_count)) / total) - console.log("Setting zenLinks", zenLinks) amendedPrefs.data["_translation_links"] = JSON.stringify(zenLinks) } amendedPrefs.ping() @@ -355,7 +355,8 @@ export default class UserRelatedState { amendedPrefs.addCallbackD((tags) => { for (const key in tags) { - if (key.startsWith("_")) { + if (key.startsWith("_") || key === "mapcomplete-language") { + // Language is managed seperately continue } this.osmConnection.GetPreference(key, undefined, { prefix: "" }).setData(tags[key]) diff --git a/Logic/UIEventSource.ts b/Logic/UIEventSource.ts index f97e8096f8..4a16550fff 100644 --- a/Logic/UIEventSource.ts +++ b/Logic/UIEventSource.ts @@ -244,8 +244,9 @@ export abstract class Store implements Readable { const self = this condition = condition ?? ((t) => t !== undefined) return new Promise((resolve) => { - if (condition(self.data)) { - resolve(self.data) + const data = self.data + if (condition(data)) { + resolve(data) } else { self.addCallbackD((data) => { resolve(data) diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index 368b9f42e4..19c1d380b4 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -591,6 +591,13 @@ export class AddEditingElements extends DesugaringStep { ): { result: LayerConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } { json = JSON.parse(JSON.stringify(json)) + if ( + json.tagRenderings && + !json.tagRenderings.some((tr) => tr === "just_created" || tr["id"] === "just_created") + ) { + json.tagRenderings.unshift(this._desugaring.tagRenderings.get("just_created")) + } + if (json.allowSplit && !ValidationUtils.hasSpecialVisualisation(json, "split_button")) { json.tagRenderings.push({ id: "split-button", diff --git a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts index d42de17d09..583294f3df 100644 --- a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts @@ -18,6 +18,12 @@ export interface TagRenderingConfigJson { */ labels?: string[] + /** + * A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question). + * This is only for advanced users + */ + classes?: string | string[] + /** * A human-readable text explaining what this tagRendering does */ diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index 1c68e0eaad..ed486cad55 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -69,7 +69,7 @@ export default class TagRenderingConfig { public readonly mappings?: Mapping[] public readonly labels: string[] - + public readonly classes: string[] constructor(json: string | QuestionableTagRenderingConfigJson, context?: string) { if (json === undefined) { throw "Initing a TagRenderingConfig with undefined in " + context @@ -110,6 +110,11 @@ export default class TagRenderingConfig { } this.labels = json.labels ?? [] + if (typeof json.classes === "string") { + this.classes = json.classes.split(" ") + } else { + this.classes = json.classes ?? [] + } this.render = Translations.T(json.render, translationKey + ".render") this.question = Translations.T(json.question, translationKey + ".question") this.questionhint = Translations.T(json.questionHint, translationKey + ".questionHint") diff --git a/UI/AllThemesGui.ts b/UI/AllThemesGui.ts index 605f78fcc1..d9e93ad364 100644 --- a/UI/AllThemesGui.ts +++ b/UI/AllThemesGui.ts @@ -4,7 +4,6 @@ import Combine from "./Base/Combine" import MoreScreen from "./BigComponents/MoreScreen" import Translations from "./i18n/Translations" import Constants from "../Models/Constants" -import { Utils } from "../Utils" import LanguagePicker from "./LanguagePicker" import IndexText from "./BigComponents/IndexText" import { ImportViewerLinks } from "./BigComponents/UserInformation" @@ -31,10 +30,8 @@ export default class AllThemesGui { featureSwitchUserbadge: new ImmutableStore(true), }), new ImportViewerLinks(state.osmConnection), - Translations.t.general.aboutMapcomplete - .Subs({ osmcha_link: Utils.OsmChaLinkFor(7) }) - .SetClass("link-underline"), - new FixedUiElement("v" + Constants.vNumber), + Translations.t.general.aboutMapComplete.intro.SetClass("link-underline"), + new FixedUiElement("v" + Constants.vNumber).SetClass("block"), ]) .SetClass("block m-5 lg:w-3/4 lg:ml-40") .AttachTo("main") diff --git a/UI/Base/Hotkeys.ts b/UI/Base/Hotkeys.ts index 243417fdb9..b260705cf9 100644 --- a/UI/Base/Hotkeys.ts +++ b/UI/Base/Hotkeys.ts @@ -99,14 +99,23 @@ export default class Hotkeys { } static generateDocumentation(): BaseUIElement { - const byKey: [string, string | Translation][] = Hotkeys._docs.data + let byKey: [string, string | Translation][] = Hotkeys._docs.data .map(({ key, documentation }) => { const modifiers = Object.keys(key).filter((k) => k !== "nomod" && k !== "onUp") - const keycode: string = key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"] + let keycode: string = key["ctrl"] ?? key["shift"] ?? key["alt"] ?? key["nomod"] + if (keycode.length == 1) { + keycode = keycode.toUpperCase() + } modifiers.push(keycode) return <[string, string | Translation]>[modifiers.join("+"), documentation] }) .sort() + byKey = Utils.NoNull(byKey) + for (let i = byKey.length - 1; i > 0; i--) { + if (byKey[i - 1][0] === byKey[i][0]) { + byKey.splice(i, 1) + } + } const t = Translations.t.hotkeyDocumentation return new Combine([ new Title(t.title, 1), diff --git a/UI/BigComponents/ActionButtons.ts b/UI/BigComponents/ActionButtons.ts deleted file mode 100644 index e001ae7aea..0000000000 --- a/UI/BigComponents/ActionButtons.ts +++ /dev/null @@ -1,72 +0,0 @@ -import Combine from "../Base/Combine" -import { Store } from "../../Logic/UIEventSource" -import Translations from "../i18n/Translations" -import { SubtleButton } from "../Base/SubtleButton" -import Svg from "../../Svg" -import { Utils } from "../../Utils" -import { MapillaryLink } from "./MapillaryLink" -import { OpenIdEditor, OpenJosm } from "./CopyrightPanel" -import Toggle from "../Input/Toggle" -import { SpecialVisualizationState } from "../SpecialVisualization" - -export class BackToThemeOverview extends Toggle { - constructor( - state: { - readonly featureSwitchMoreQuests: Store - }, - options: { - imgSize: string - } - ) { - const t = Translations.t.general - const button = new SubtleButton(Svg.add_ui(), t.backToIndex, options).onClick(() => { - const path = window.location.href.split("/") - path.pop() - path.push("index.html") - window.location.href = path.join("/") - }) - - super(button, undefined, state.featureSwitchMoreQuests) - } -} - -export class ActionButtons extends Combine { - constructor(state:SpecialVisualizationState) { - const imgSize = "h-6 w-6" - const iconStyle = "height: 1.5rem; width: 1.5rem" - const t = Translations.t.general.attribution - - super([ - new BackToThemeOverview(state, { imgSize }), - - new SubtleButton(Svg.liberapay_ui(), t.donate, { - url: "https://liberapay.com/pietervdvn/", - newTab: true, - imgSize, - }), - new SubtleButton(Svg.bug_ui(), t.openIssueTracker, { - url: "https://github.com/pietervdvn/MapComplete/issues", - newTab: true, - imgSize, - }), - new SubtleButton( - Svg.statistics_ui(), - t.openOsmcha.Subs({ theme: state.layoutToUse.title }), - { - url: Utils.OsmChaLinkFor(31, state.layoutToUse.id), - newTab: true, - imgSize, - } - ), - new SubtleButton(Svg.mastodon_ui(), t.followOnMastodon, { - url: "https://en.osm.town/@MapComplete", - newTab: true, - imgSize, - }), - new OpenIdEditor(state, iconStyle), - new MapillaryLink(state, iconStyle), - new OpenJosm(state.osmConnection,state.mapProperties.bounds, iconStyle).SetClass("hidden-on-mobile"), - ]) - this.SetClass("block w-full link-no-underline") - } -} diff --git a/UI/BigComponents/AllDownloads.ts b/UI/BigComponents/AllDownloads.ts index d8166e2d94..33ea33a495 100644 --- a/UI/BigComponents/AllDownloads.ts +++ b/UI/BigComponents/AllDownloads.ts @@ -4,7 +4,6 @@ import Translations from "../i18n/Translations" import { UIEventSource } from "../../Logic/UIEventSource" import BaseUIElement from "../BaseUIElement" import Toggle from "../Input/Toggle" -import { DownloadPanel } from "./DownloadPanel" import { SubtleButton } from "../Base/SubtleButton" import Svg from "../../Svg" import ExportPDF from "../ExportPDF" @@ -79,13 +78,6 @@ export default class AllDownloads extends ScrollableFullScreen { isExporting ) - const pdf = new Toggle( - new SubtleButton(icon, text), - undefined, - - state.featureSwitchExportAsPdf - ) - - - return pdf + return new SubtleButton(icon, text) + } } diff --git a/UI/BigComponents/SelectedElementView.svelte b/UI/BigComponents/SelectedElementView.svelte index 5899e05762..74b9415bde 100644 --- a/UI/BigComponents/SelectedElementView.svelte +++ b/UI/BigComponents/SelectedElementView.svelte @@ -6,6 +6,8 @@ import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte"; import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"; import { onDestroy } from "svelte"; + import Translations from "../i18n/Translations"; + import Tr from "../Base/Tr.svelte"; export let state: SpecialVisualizationState; export let layer: LayerConfig; @@ -18,39 +20,45 @@ onDestroy(tags.addCallbackAndRun(tags => { _tags = tags; })); - - let _metatags: Record - onDestroy(state.userRelatedState.preferencesAsTags .addCallbackAndRun(tags => { + + let _metatags: Record; + onDestroy(state.userRelatedState.preferencesAsTags.addCallbackAndRun(tags => { _metatags = tags; })); -
-
- -

- -

-
- {#each layer.titleIcons as titleIconConfig (titleIconConfig.id)} -
- -
+{#if _tags._deleted === "yes"} + +{:else} +
+
+ +

+ +

+ +
+ {#each layer.titleIcons as titleIconConfig (titleIconConfig.id)} +
+ +
+ {/each} +
+ + +
+ +
+ {#each layer.tagRenderings as config (config.id)} + {#if (config.condition === undefined || config.condition.matchesProperties(_tags)) && (config.metacondition === undefined || config.metacondition.matchesProperties({ ..._tags, ..._metatags }))} + {#if config.IsKnown(_tags)} + + {/if} + {/if} {/each}
-
- -
- {#each layer.tagRenderings as config (config.id)} - {#if (config.condition === undefined || config.condition.matchesProperties(_tags)) && (config.metacondition === undefined || config.metacondition.matchesProperties(_metatags))} - {#if config.IsKnown(_tags)} - - {/if} - {/if} - {/each} -
- -
+{/if} diff --git a/UI/BigComponents/ThemeIntroductionPanel.ts b/UI/BigComponents/ThemeIntroductionPanel.ts index 587cc0be43..35caf06b4f 100644 --- a/UI/BigComponents/ThemeIntroductionPanel.ts +++ b/UI/BigComponents/ThemeIntroductionPanel.ts @@ -8,7 +8,6 @@ import { LoginToggle } from "../Popup/LoginButton" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { OsmConnection } from "../../Logic/Osm/OsmConnection" import LoggedInUserIndicator from "../LoggedInUserIndicator" -import { ActionButtons } from "./ActionButtons" import { BBox } from "../../Logic/BBox" import Loc from "../../Models/Loc" import { DefaultGuiState } from "../DefaultGuiState" @@ -80,7 +79,6 @@ export default class ThemeIntroductionPanel extends Combine { layout.descriptionTail?.Clone().SetClass("block mt-4"), languagePicker?.SetClass("block mt-4 pb-8 border-b-2 border-dotted border-gray-400"), - new ActionButtons(state), ...layout.CustomCodeSnippets(), ]) diff --git a/UI/InputElement/Helpers/LocationInput.svelte b/UI/InputElement/Helpers/LocationInput.svelte index 9ca0175f26..9ff4adc565 100644 --- a/UI/InputElement/Helpers/LocationInput.svelte +++ b/UI/InputElement/Helpers/LocationInput.svelte @@ -14,7 +14,7 @@ /** * Called when setup is done, can be used to add more layers to the map */ - export let onCreated : (value: Store<{lon: number, lat: number}> , map: Store, mapProperties: MapProperties ) => void + export let onCreated : (value: Store<{lon: number, lat: number}> , map: Store, mapProperties: MapProperties ) => void = undefined export let map: UIEventSource = new UIEventSource(undefined); let mla = new MapLibreAdaptor(map, mapProperties); diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index b3de1bb380..a0345a7355 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -240,13 +240,10 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { container.style.height = document.documentElement.clientHeight + "px" } - await html2canvas( - map.getCanvasContainer(), - { - backgroundColor: "#00000000", - canvas: drawOn, - } - ) + await html2canvas(map.getCanvasContainer(), { + backgroundColor: "#00000000", + canvas: drawOn, + }) } catch (e) { console.error(e) } finally { @@ -261,7 +258,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { private updateStores() { const map = this._maplibreMap.data - if (map === undefined) { + if (!map) { return } const dt = this.location.data diff --git a/UI/Map/ShowDataLayer.ts b/UI/Map/ShowDataLayer.ts index efcfe4b358..7c1189ab1b 100644 --- a/UI/Map/ShowDataLayer.ts +++ b/UI/Map/ShowDataLayer.ts @@ -9,7 +9,6 @@ import { OsmTags } from "../../Models/OsmFeature" import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource" import { BBox } from "../../Logic/BBox" import { Feature, Point } from "geojson" -import ScrollableFullScreen from "../Base/ScrollableFullScreen" import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" import { Utils } from "../../Utils" import * as range_layer from "../../assets/layers/range/range.json" @@ -360,7 +359,6 @@ export default class ShowDataLayer { drawMarkers?: true | boolean drawLines?: true | boolean } - private readonly _popupCache: Map constructor( map: Store, @@ -372,7 +370,6 @@ export default class ShowDataLayer { ) { this._map = map this._options = options - this._popupCache = new Map() const self = this map.addCallbackAndRunD((map) => self.initDrawFeatures(map)) } diff --git a/UI/Popup/AddNewPoint/AddNewPoint.svelte b/UI/Popup/AddNewPoint/AddNewPoint.svelte index 9b60da791d..1a31ba7a44 100644 --- a/UI/Popup/AddNewPoint/AddNewPoint.svelte +++ b/UI/Popup/AddNewPoint/AddNewPoint.svelte @@ -88,16 +88,18 @@ changeType: "create", snapOnto: snapToWay }); - await state.changes.applyAction(newElementAction); + await state.changes.applyAction(newElementAction) + state.newFeatures.features.ping() // The 'changes' should have created a new point, which added this into the 'featureProperties' const newId = newElementAction.newElementId; - + console.log("Applied pending changes, fetching store for", newId) const tagsStore = state.featureProperties.getStore(newId); { // Set some metainfo const properties = tagsStore.data; if (snapTo) { // metatags (starting with underscore) are not uploaded, so we can safely mark this + delete properties["_referencing_ways"] properties["_referencing_ways"] = `["${snapTo}"]`; } properties["_backend"] = state.osmConnection.Backend() diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index fb19e82bbf..8b358059e7 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -1,15 +1,9 @@ -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import QuestionBox from "./QuestionBox" +import { UIEventSource } from "../../Logic/UIEventSource" import Combine from "../Base/Combine" -import TagRenderingAnswer from "./TagRenderingAnswer" import ScrollableFullScreen from "../Base/ScrollableFullScreen" -import Constants from "../../Models/Constants" -import SharedTagRenderings from "../../Customizations/SharedTagRenderings" import BaseUIElement from "../BaseUIElement" -import { VariableUiElement } from "../Base/VariableUIElement" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import Toggle from "../Input/Toggle" -import Lazy from "../Base/Lazy" import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import Svg from "../../Svg" import Translations from "../i18n/Translations" @@ -31,7 +25,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { ) super( () => undefined, - () => FeatureInfoBox.GenerateContent(tags, layerConfig, state, showAllQuestions), + () => FeatureInfoBox.GenerateContent(tags, layerConfig), options?.hashToShow ?? tags.data.id ?? "item", options?.isShown, options @@ -42,60 +36,14 @@ export default class FeatureInfoBox extends ScrollableFullScreen { } } - public static GenerateContent( - tags: UIEventSource, - layerConfig: LayerConfig - ): BaseUIElement { + public static GenerateContent(tags: UIEventSource): BaseUIElement { return new Toggle( new Combine([ Svg.delete_icon_svg().SetClass("w-8 h-8"), Translations.t.delete.isDeleted, ]).SetClass("flex justify-center font-bold items-center"), - FeatureInfoBox.GenerateMainContent(tags, layerConfig), + new Combine([]).SetClass("block"), tags.map((t) => t["_deleted"] == "yes") ) } - private static GenerateMainContent( - tags: UIEventSource, - layerConfig: LayerConfig - ): BaseUIElement { - const t = Translations.t.general - - const withQuestion = layerConfig.tagRenderings.filter( - (tr) => tr.question !== undefined - ).length - - const allRenderings: BaseUIElement[] = [ - new VariableUiElement( - tags - .map((data) => data["_newly_created"]) - .map((isCreated) => { - if (isCreated === undefined) { - return undefined - } - const createdDate = new Date(isCreated) - const secondsSinceCreation = (Date.now() - createdDate.getTime()) / 1000 - if (secondsSinceCreation >= 60 * 5) { - return undefined - } - - const els = [] - const thanks = new Combine([ - Svg.party_svg().SetClass( - "w-12 h-12 shrink-0 p-1 m-1 bg-white rounded-full block" - ), - t.newlyCreated, - ]).SetClass("flex w-full thanks content-center") - els.push(thanks) - if (withQuestion > 0) { - els.push(t.feelFreeToSkip) - } - - return new Combine(els).SetClass("pb-4 mb-4 border-b block border-black") - }) - ), - ] - - return new Combine(allRenderings).SetClass("block") - } } diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index 9abed2cbb0..5bdf8d4cbc 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -65,7 +65,6 @@ export default class SplitRoadWizard extends Combine { leafletMap.setData(mapComponent.SetClass("w-full h-80")) ) } - initMap() // Toggle between splitmap const splitButton = new SubtleButton( @@ -94,7 +93,6 @@ export default class SplitRoadWizard extends Combine { await state.changes?.applyAction(splitAction) // We throw away the old map and splitpoints, and create a new map from scratch splitPoints.setData([]) - initMap() // Close the popup. The contributor has to select a segment again to make sure they continue editing the correct segment; see #1219 state.selectedElement?.setData(undefined) @@ -134,6 +132,11 @@ export default class SplitRoadWizard extends Combine { ), new Toggle(mapView, splitToggle, splitClicked), ]) + splitClicked.addCallback((view) => { + if (view) { + initMap() + } + }) this.dialogIsOpened = splitClicked const self = this splitButton.onClick(() => { diff --git a/UI/Popup/TagRendering/Questionbox.svelte b/UI/Popup/TagRendering/Questionbox.svelte index 2daecabd72..b095c5df51 100644 --- a/UI/Popup/TagRendering/Questionbox.svelte +++ b/UI/Popup/TagRendering/Questionbox.svelte @@ -45,7 +45,6 @@ let questionsToAsk = tags.map(tags => { const baseQuestions = (layer.tagRenderings ?? [])?.filter(tr => allowed(tr.labels) && tr.question !== undefined); - console.log("Determining questions for", baseQuestions) const questionsToAsk: TagRenderingConfig[] = []; for (const baseQuestion of baseQuestions) { if (skippedQuestions.data.has(baseQuestion.id) > 0) { diff --git a/UI/Popup/TagRendering/TagRenderingAnswer.svelte b/UI/Popup/TagRendering/TagRenderingAnswer.svelte index 1a583e282e..431814777a 100644 --- a/UI/Popup/TagRendering/TagRenderingAnswer.svelte +++ b/UI/Popup/TagRendering/TagRenderingAnswer.svelte @@ -23,10 +23,12 @@ export let layer: LayerConfig; let trs: { then: Translation; icon?: string; iconClass?: string }[]; $: trs = Utils.NoNull(config?.GetRenderValues(_tags)); + let classes = "" + $:classes = config?.classes?.join(" ") ?? ""; {#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties(_tags))} -
+
{#if trs.length === 1} {/if} diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 02f64d1b67..1152f3bf07 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -22,7 +22,6 @@ import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"; import FloatOver from "./Base/FloatOver.svelte"; import PrivacyPolicy from "./BigComponents/PrivacyPolicy"; - import { Utils } from "../Utils"; import Constants from "../Models/Constants"; import TabbedGroup from "./Base/TabbedGroup.svelte"; import UserRelatedState from "../Logic/State/UserRelatedState"; @@ -31,6 +30,8 @@ import CopyrightPanel from "./BigComponents/CopyrightPanel"; import { DownloadPanel } from "./BigComponents/DownloadPanel"; import ModalRight from "./Base/ModalRight.svelte"; + import { Utils } from "../Utils"; + import Hotkeys from "./Base/Hotkeys"; export let state: ThemeViewState; let layout = state.layout; @@ -95,7 +96,8 @@
- +
@@ -152,20 +154,22 @@
-
- - +
+ + + +
- new DownloadPanel(state)}/> + new DownloadPanel(state)} />
- +
- +
- - new CopyrightPanel(state)}> - + + new CopyrightPanel(state)} slot="content3"> + @@ -177,17 +181,43 @@ state.guistate.menuIsOpened.setData(false)}>
- +
- + + + + + + + + + + + + + + + + + + + + + + + + + + + {Constants.vNumber} +
+
@@ -199,16 +229,16 @@
- +
diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts index baa1b43328..9b9c98e2ae 100644 --- a/UI/i18n/Translations.ts +++ b/UI/i18n/Translations.ts @@ -5,8 +5,7 @@ import CompiledTranslations from "../../assets/generated/CompiledTranslations" import LanguageUtils from "../../Utils/LanguageUtils" export default class Translations { - static readonly t: typeof CompiledTranslations.t & Readonly = - CompiledTranslations.t + static readonly t: Readonly = CompiledTranslations.t private static knownLanguages = LanguageUtils.usedLanguages constructor() { throw "Translations is static. If you want to intitialize a new translation, use the singular form" diff --git a/Utils.ts b/Utils.ts index 9917f42eee..20246c84d3 100644 --- a/Utils.ts +++ b/Utils.ts @@ -1164,6 +1164,16 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be } } + public static HomepageLink(): string { + if (typeof window === "undefined") { + return "https://mapcomplete.osm.be" + } + const path = window.location.href.split("/") + path.pop() + path.push("index.html") + return path.join("/") + } + public static OsmChaLinkFor(daysInThePast, theme = undefined): string { const now = new Date() const lastWeek = new Date(now.getTime() - daysInThePast * 24 * 60 * 60 * 1000) diff --git a/assets/layers/address/address.json b/assets/layers/address/address.json index 49a9a112bb..f903dc1c1e 100644 --- a/assets/layers/address/address.json +++ b/assets/layers/address/address.json @@ -18,7 +18,8 @@ "cs": "Známé adresy v OSM", "pa_PK": "او‌ایس‌ایم وچ جاݨ پچھاݨے پتے", "ca": "Adreces conegudes a OSM", - "zgh": "ⴰⵏⵙⵉⵡⵏ ⵉⵜⵜⵡⴰⵙⵙⵏⵏ ⴳ OSM" + "zgh": "ⴰⵏⵙⵉⵡⵏ ⵉⵜⵜⵡⴰⵙⵙⵏⵏ ⴳ OSM", + "pt_BR": "Endereços conhecidos no OSM" }, "minzoom": 18, "source": { @@ -50,7 +51,8 @@ "nb_NO": "Kjent adresse", "da": "Kendt adresse", "cs": "Známá adresa", - "ca": "Adreça coneguda" + "ca": "Adreça coneguda", + "pt_BR": "Endereço conhecido" } }, "description": { @@ -72,7 +74,8 @@ "eo": "Adresoj", "cs": "Adresy", "pa_PK": "پتے", - "zgh": "ⴰⵏⵙⵉⵡⵏ" + "zgh": "ⴰⵏⵙⵉⵡⵏ", + "pt_BR": "Endereços" }, "tagRenderings": [ { @@ -92,7 +95,8 @@ "cs": "Číslo domu je {addr:housenumber}", "pt": "The house number is {addr:housenumber}", "nb_NO": "Husnummeret er {addr:housenumber}", - "ca": "El número de porta és {addr:housenumber}" + "ca": "El número de porta és {addr:housenumber}", + "pt_BR": "O número da casa é {addr:housenumber}" }, "question": { "en": "What is the number of this house?", @@ -109,7 +113,8 @@ "cs": "Jaké je číslo tohoto domu?", "pt": "Qual é o número desta casa?", "nb_NO": "Hvilket husnummer har dette huset?", - "ca": "Quin és el número d'aquesta casa?" + "ca": "Quin és el número d'aquesta casa?", + "pt_BR": "Qual é o número desta casa?" }, "freeform": { "key": "addr:housenumber", @@ -140,7 +145,8 @@ "cs": "Tato budova nemá číslo domu", "pt": "Este prédio não tem número", "nb_NO": "Denne bygningen har ikke noe husnummer", - "ca": "Aquest edifici no té número" + "ca": "Aquest edifici no té número", + "pt_BR": "Este prédio não tem número" } } ] @@ -220,7 +226,8 @@ "cs": "Co by se zde mělo opravit? Vysvětlete to, prosím", "pt": "O que deve ser corrigido aqui? Explique", "nb_NO": "Hva bør fikses her? Forklar.", - "ca": "Què s’hauria de corregir aquí? Exposa-ho" + "ca": "Què s’hauria de corregir aquí? Exposa-ho", + "pt_BR": "O que deve ser corrigido aqui? Explique" }, "freeform": { "key": "fixme" diff --git a/assets/layers/advertising/advertising.json b/assets/layers/advertising/advertising.json index 3573d611b3..580cb2f574 100644 --- a/assets/layers/advertising/advertising.json +++ b/assets/layers/advertising/advertising.json @@ -42,7 +42,8 @@ "es": "Tablon de anuncios", "en": "Board", "de": "Brett", - "cs": "Deska" + "cs": "Deska", + "fr": "Petit panneau" } }, { @@ -71,7 +72,8 @@ "en": "Column", "de": "Litfaßsäule", "cs": "Sloup", - "nl": "Aanplakzuil" + "nl": "Aanplakzuil", + "fr": "Colonne" } }, { @@ -86,7 +88,8 @@ "en": "Flag", "de": "Flagge", "cs": "Vlajka", - "nl": "Vlag" + "nl": "Vlag", + "fr": "Drapeau" } }, { @@ -101,7 +104,8 @@ "en": "Screen", "de": "Bildschirm", "cs": "Obrazovka", - "nl": "Scherm" + "nl": "Scherm", + "fr": "Écran" } }, { @@ -116,7 +120,8 @@ "en": "Sculpture", "de": "Skulptur", "cs": "Socha", - "nl": "Sculptuur" + "nl": "Sculptuur", + "fr": "Sculpture" } }, { @@ -130,7 +135,9 @@ "es": "Cartel", "en": "Sign", "de": "Schild", - "cs": "Cedule" + "cs": "Cedule", + "fr": "Enseigne", + "pt_BR": "Placa" } }, { @@ -145,7 +152,8 @@ "en": "Tarp", "de": "Plane", "cs": "Plachta", - "nl": "Spandoek" + "nl": "Spandoek", + "fr": "Bâche" } }, { @@ -160,7 +168,8 @@ "en": "Totem", "de": "Totem", "cs": "Totem", - "nl": "Aanplakzuil" + "nl": "Aanplakzuil", + "fr": "Totem" } }, { @@ -175,7 +184,8 @@ "en": "Wall painting", "de": "Wandmalerei", "cs": "Nástěnná malba", - "nl": "Muurschildering" + "nl": "Muurschildering", + "fr": "Peinture murale" } } ] @@ -262,7 +272,8 @@ "de": "Dies ist eine Litfaßsäule", "cs": "Toto je sloup", "fr": "C'est une colonne", - "nl": "Dit is een aanplakzuil" + "nl": "Dit is een aanplakzuil", + "pt_BR": "Isto é uma coluna" }, "icon": { "path": "./assets/themes/advertising/column.svg", @@ -282,7 +293,8 @@ "de": "Dies ist eine Flagge", "cs": "Toto je vlajka", "fr": "C'est un drapeau", - "nl": "Dit is een vlag" + "nl": "Dit is een vlag", + "pt_BR": "Isto é uma bandeira" }, "icon": { "path": "./assets/themes/advertising/flag.svg", @@ -358,7 +370,8 @@ "en": "This is a sign", "de": "Dies ist ein Schild", "cs": "Toto je cedule", - "fr": "C'est une enseigne (indique le nom du lieu/magasin)" + "fr": "C'est une enseigne (indique le nom du lieu/magasin)", + "pt_BR": "Isto é uma placa" }, "icon": { "path": "./assets/themes/advertising/sign.svg", @@ -565,7 +578,8 @@ "de": "Betrieben von {operator}", "cs": "Provozuje {operator}", "fr": "Exploité par {operator}", - "nl": "Uitgebaat door {operator}" + "nl": "Uitgebaat door {operator}", + "pt_BR": "Operado por {operator}" }, "question": { "ca": "Qui opera aquest element?", @@ -589,7 +603,8 @@ "en": "What kind of message is shown?", "de": "Welche Art von Nachricht wird angezeigt?", "cs": "Jaký typ zprávy je zobrazen?", - "nl": "Wat voor boodschap wordt hier getoond?" + "nl": "Wat voor boodschap wordt hier getoond?", + "fr": "Quel est le type de message affiché ?" }, "mappings": [ { @@ -601,7 +616,8 @@ "en": "Commercial message", "de": "Werbebotschaft", "cs": "Komerční sdělení", - "fr": "Message commercial" + "fr": "Message commercial", + "pt_BR": "Mensagem comercial" } }, { @@ -704,7 +720,8 @@ "de": "Religiöse Botschaft", "cs": "Náboženská zpráva", "fr": "Message religieux", - "nl": "Religieuze boodschap" + "nl": "Religieuze boodschap", + "pt_BR": "Mensagem religiosa" } }, { @@ -734,7 +751,8 @@ "de": "eine Karte", "cs": "Mapa", "fr": "Une carte", - "nl": "Een kaart" + "nl": "Een kaart", + "pt_BR": "Um mapa" } } ], @@ -767,8 +785,8 @@ "if": "sides=1", "then": { "en": "This object has advertisements on a single side", - "ca": "Aquest mupi té publicitat a un únic costat", - "es": "Este mupi tiene publicidad en un único lado", + "ca": "Aquest objecte té publicitat a un únic costat", + "es": "Este objeto tiene publicidad en un único lado", "de": "Werbung wird nur auf einer Seite angezeigt", "cs": "Tento objekt má reklamy na jedné straně", "fr": "Cet objet a de la publicité sur un seul côté" @@ -778,8 +796,8 @@ "if": "sides=2", "then": { "en": "This object has advertisements on both sides", - "ca": "Aquest mupi té publicitat pels dos costas", - "es": "Este mupi tiene publicidad por los dos lados", + "ca": "Aquest objecte té publicitat pels dos costas", + "es": "Este objeto tiene publicidad por los dos lados", "de": "Werbung wird auf beiden Seiten angezeigt", "cs": "Tento objekt má reklamy na obou stranách", "fr": "Cet objet a de la publicité des deux côtés" @@ -796,7 +814,8 @@ "de": "Die Referenznummer lautet {ref}", "cs": "Referenční číslo je {ref}", "fr": "Le numéro de référence est {ref}", - "nl": "Het referentienummer is {ref}" + "nl": "Het referentienummer is {ref}", + "pt_BR": "O número de referência é {ref}" }, "question": { "ca": "Quin és el número de refèrencia?", @@ -1015,12 +1034,13 @@ "advertising=board" ], "title": { - "ca": "un tauló d'anunis", + "ca": "un tauló d'anuncis", "es": "un tablón de anuncios", "en": "a board", "de": "ein Anschlagbrett", "cs": "billboard", - "nl": "een uithangbord" + "nl": "een uithangbord", + "fr": "un petit panneau" }, "description": { "en": "Small billboard for neighbourhood advertising, generally intended for pedestrians", @@ -1047,7 +1067,8 @@ "en": "a column", "de": "eine Litfaßsäule", "cs": "sloup", - "nl": "een aanplakzuil" + "nl": "een aanplakzuil", + "fr": "une colonne" }, "description": { "en": "A cylindrical outdoor structure which shows advertisements", @@ -1074,7 +1095,8 @@ "en": "a flag", "de": "eine Flagge", "cs": "vlajka", - "nl": "een vlag" + "nl": "een vlag", + "fr": "un drapeau" }, "exampleImages": [ "./assets/themes/advertising/Advertising_flag.jpg", @@ -1091,7 +1113,8 @@ "en": "a screen", "de": "einen Bildschirm", "cs": "obrazovka", - "nl": "een scherm" + "nl": "een scherm", + "fr": "un écran" }, "exampleImages": [ "./assets/themes/advertising/Screen_poster_box.jpg", @@ -1108,7 +1131,8 @@ "en": "a screen mounted on a wall", "de": "ein wandmontierter Bildschirm", "cs": "obrazovka připevněná na stěnu", - "nl": "een scherm op een muur" + "nl": "een scherm op een muur", + "fr": "un écran fixé au mur" }, "preciseInput": { "preferredBackground": "map", @@ -1131,7 +1155,8 @@ "en": "a tarp", "de": "eine Plane", "cs": "plachta", - "nl": "een spandoek" + "nl": "een spandoek", + "fr": "une bâche" }, "description": { "en": "A piece of waterproof textile with a printed message, permanently anchored on a wall", @@ -1160,7 +1185,8 @@ "es": "un tótem", "en": "a totem", "de": "ein Totem", - "cs": "totem" + "cs": "totem", + "fr": "un totem" }, "exampleImages": [ "./assets/themes/advertising/AdvertisingTotem_004.jpg", @@ -1177,7 +1203,9 @@ "es": "un lletrer", "en": "a sign", "de": "ein Schild", - "cs": "cedule" + "cs": "cedule", + "fr": "une enseigne", + "pt_BR": "uma placa" }, "preciseInput": { "preferredBackground": "map", @@ -1207,7 +1235,8 @@ "es": "una escultura", "en": "a sculpture", "de": "eine Skulptur", - "cs": "socha" + "cs": "socha", + "fr": "une sculpture" }, "exampleImages": [ "./assets/themes/advertising/Aircraft_Sculpture.jpg", @@ -1224,7 +1253,8 @@ "es": "una pared pintada", "en": "a wall painting", "de": "eine Wandmalerei", - "cs": "nástěnná malba" + "cs": "nástěnná malba", + "fr": "une peinture murale" }, "preciseInput": { "preferredBackground": "map", diff --git a/assets/layers/artwork/artwork.json b/assets/layers/artwork/artwork.json index 158975f433..c42d88b372 100644 --- a/assets/layers/artwork/artwork.json +++ b/assets/layers/artwork/artwork.json @@ -581,7 +581,7 @@ "render": { "en": "More information on this website", "nl": "Meer informatie op deze website", - "fr": "Plus d'info sûr ce site web", + "fr": "Plus d'infos sur ce site web", "de": "Weitere Informationen auf dieser Webseite", "id": "Info lanjut tersedia di laman web ini", "it": "Ulteriori informazioni su questo sito web", @@ -632,7 +632,8 @@ "pt": "A obra de arte representa {wikidata_label(subject:wikidata)}{wikipedia(subject:wikidata)}", "es": "Esta obra de arte representa {wikidata_label(subject:wikidata)}{wikipedia(subject:wikidata)}", "nb_NO": "Dette kunstverket viser {wikidata_label(subject:wikidata)}{wikipedia(subject:wikidata)}", - "ca": "Aquesta obra d'art representa {wikidata_label(subject:wikidata)}{wikipedia(subject:wikidata)}" + "ca": "Aquesta obra d'art representa {wikidata_label(subject:wikidata)}{wikipedia(subject:wikidata)}", + "fr": "Cette œuvre dépeint {wikidata_label(subject:wikidata)}{wikipedia(subject:wikidata)}" }, "labels": [ "artwork-question" @@ -646,7 +647,8 @@ "fr": "Cette oeuvre d'art sert-elle de banc ?", "nl": "Is dit kunstwerk ook een zitbank?", "nb_NO": "Tjener dette kunstverket funksjonen som benk?", - "ca": "Aquesta obra d'art serveix com a un banc?" + "ca": "Aquesta obra d'art serveix com a un banc?", + "cs": "Slouží toto umělecké dílo jako lavička?" }, "mappings": [ { @@ -656,7 +658,8 @@ "de": "Dieses Kunstwerk dient auch als Sitzbank", "fr": "Cette oeuvre d'art sert aussi de banc", "nl": "Dit kunstwerk doet ook dienst als zitbank", - "ca": "Aquesta obra d'art també serveix com a banc" + "ca": "Aquesta obra d'art també serveix com a banc", + "cs": "Toto umělecké dílo slouží také jako lavička" } }, { @@ -667,7 +670,8 @@ "fr": "Cette oeuvre d'art ne sert pas de banc", "nl": "Dit kunstwerk doet geen dienst als zitbank", "nb_NO": "Dette kunstverket tjener ikke funksjonen som benk", - "ca": "Aquesta obra d'art no serveix com a banc" + "ca": "Aquesta obra d'art no serveix com a banc", + "cs": "Toto umělecké dílo neslouží jako lavička" } }, { @@ -678,7 +682,8 @@ "fr": "Cette oeuvre d'art ne sert pas de banc", "nl": "Dit kunstwerk doet geen dienst als zitbank", "nb_NO": "Dette kunstverket tjener ikke den hensikten å være en benk", - "ca": "Aquesta obra d'art no serveix com a un banc" + "ca": "Aquesta obra d'art no serveix com a un banc", + "cs": "Toto umělecké dílo neslouží jako lavička" }, "hideInAnswer": true } @@ -726,4 +731,4 @@ "filter": [ "has_image" ] -} +} \ No newline at end of file diff --git a/assets/layers/atm/atm.json b/assets/layers/atm/atm.json index 21501bf93c..6808a8beab 100644 --- a/assets/layers/atm/atm.json +++ b/assets/layers/atm/atm.json @@ -6,7 +6,8 @@ "fr": "DABs", "nl": "Geldautomaten", "ca": "Caixers Automàtics", - "nb_NO": "Minibanker" + "nb_NO": "Minibanker", + "cs": "Bankomaty" }, "description": { "en": "ATMs to withdraw money", @@ -24,7 +25,8 @@ "fr": "DAB", "nl": "Geldautomaat", "nb_NO": "Minibank", - "ca": "Caixer Automàtic" + "ca": "Caixer Automàtic", + "cs": "Bankomat" }, "mappings": [ { @@ -35,7 +37,8 @@ "fr": "DAB {brand}", "nl": "{brand} Geldautomaat", "nb_NO": "{brand}-minibank", - "ca": "Caixer automàtic {brand}" + "ca": "Caixer automàtic {brand}", + "cs": "Bankomat {brand}" } } ] @@ -55,7 +58,8 @@ "fr": "un DAB", "nl": "een geldautomaat", "ca": "un caixer automàtic", - "nb_NO": "en minibank" + "nb_NO": "en minibank", + "cs": "bankomat" } } ], @@ -69,7 +73,8 @@ "fr": "Le nom de ce DAB est {name}", "nl": "De naam van deze geldautomaat is {name}", "ca": "El nom d'aquest caixer és {name}", - "nb_NO": "Navnet på denne minibanken er {name}" + "nb_NO": "Navnet på denne minibanken er {name}", + "cs": "Název tohoto bankomatu je {name}" }, "condition": "name~*" }, @@ -81,7 +86,8 @@ "fr": "De quelle marque est ce DAB ?", "nl": "Van welk merk is deze geldautomaat?", "ca": "De quina marca és aquest caixer?", - "nb_NO": "Hvilet merke har denne minibanken?" + "nb_NO": "Hvilet merke har denne minibanken?", + "cs": "Jaká je značka bankomatu?" }, "freeform": { "key": "brand", @@ -92,7 +98,8 @@ "fr": "Nom de marque", "nl": "Merknaam", "nb_NO": "Merkenavn", - "ca": "Nom de la marca" + "ca": "Nom de la marca", + "cs": "Obchodní značka" } }, "render": { @@ -101,7 +108,8 @@ "fr": "La marque de ce DAB est {brand}", "nl": "Het merk van deze geldautomaat is {brand}", "nb_NO": "Merkenavnet for denne minibanken er {brand}", - "ca": "La marca d'aquest caixer és {brand}" + "ca": "La marca d'aquest caixer és {brand}", + "cs": "Značka tohoto bankomatu je {brand}" } }, { @@ -113,7 +121,8 @@ "fr": "Quelle société exploite ce DAB ?", "nl": "Welk bedrijf beheert deze geldautomaat?", "nb_NO": "Hvilket selskap driver denne minibanken?", - "ca": "Quina companyia opera aquest caixer?" + "ca": "Quina companyia opera aquest caixer?", + "cs": "Která společnost provozuje tento bankomat?" }, "freeform": { "key": "operator", @@ -123,7 +132,8 @@ "de": "Betreiber", "fr": "Opérateur", "nl": "Beheerder", - "ca": "Operador" + "ca": "Operador", + "cs": "Operátor" } }, "render": { @@ -132,7 +142,8 @@ "fr": "Ce DAB est exploité par {operator}", "nl": "Deze geldautomaat wordt beheerd door {operator}", "nb_NO": "Minibanken drives av {operator}", - "ca": "{operator} opera aquest caixer" + "ca": "{operator} opera aquest caixer", + "cs": "Bankomat provozuje {operator}" } }, "opening_hours", @@ -143,7 +154,8 @@ "de": "Kann man an diesem Geldautomaten Bargeld abheben?", "nl": "Kan je geld ophalen bij deze geldautomaat?", "nb_NO": "Kan man gjøre uttak fra denne minibanken?", - "ca": "Pots retirar diners des d'aquest caixer?" + "ca": "Pots retirar diners des d'aquest caixer?", + "cs": "Lze z tohoto bankomatu vybírat hotovost?" }, "mappings": [ { @@ -153,7 +165,8 @@ "de": "Sie können an diesem Geldautomaten Bargeld abheben", "nl": "Je kan geld ophalen bij deze geldautomaat", "ca": "Pots retirar diners a aquest caixer", - "nb_NO": "Du kan gjøre uttak i denne minibanken" + "nb_NO": "Du kan gjøre uttak i denne minibanken", + "cs": "Z tohoto bankomatu můžete vybírat hotovost" }, "hideInAnswer": true }, @@ -163,7 +176,8 @@ "en": "You can withdraw cash from this ATM", "de": "An diesem Geldautomaten können Sie Bargeld abheben", "nl": "Je kan geld ophalen bij deze geldautomaat", - "ca": "Pots retirar diners des d'aquest caixer" + "ca": "Pots retirar diners des d'aquest caixer", + "cs": "Z tohoto bankomatu můžete vybírat hotovost" } }, { @@ -172,7 +186,8 @@ "en": "You cannot withdraw cash from this ATM", "de": "Sie können an diesem Geldautomaten kein Bargeld abheben", "nl": "Je kan geen geld ophalen bij deze geldautomaat", - "ca": "No pots retirar diners des d'aquest caixer" + "ca": "No pots retirar diners des d'aquest caixer", + "cs": "Z tohoto bankomatu nelze vybírat hotovost" } } ] @@ -183,7 +198,9 @@ "en": "Can you deposit cash into this ATM?", "de": "Kann man an diesem Geldautomaten Bargeld einzahlen?", "nl": "Kan je geld storten bij deze geldautomaat?", - "ca": "Pots dipositar diners a aquest caixer?" + "ca": "Pots dipositar diners a aquest caixer?", + "cs": "Můžete do tohoto bankomatu vložit hotovost?", + "fr": "Pouvez-vous déposer de l'argent liquide dans ce DAB ?" }, "mappings": [ { @@ -193,7 +210,9 @@ "de": "Sie können wahrscheinlich kein Bargeld in diesen Geldautomaten einzahlen", "nl": "Je kan waarschijnlijk geen geld deponeren in deze geldautomaat", "ca": "Probablement no pots ingressar diners a aquest caixer", - "nb_NO": "Du kan antagelig ikke gjøre innskudd i denne minibanken" + "nb_NO": "Du kan antagelig ikke gjøre innskudd i denne minibanken", + "cs": "Do tohoto bankomatu pravděpodobně nelze vložit hotovost", + "fr": "Vous ne pouvez probablement pas déposer d'argent liquide dans ce DAB" }, "hideInAnswer": true }, @@ -204,7 +223,9 @@ "de": "Sie können Bargeld in diesen Geldautomaten einzahlen", "nl": "Je kan geld deponeren in deze geldautomaat", "nb_NO": "Du kan ikke gjøre innskudd i denne minibanken", - "ca": "Pots dipositar diners a aquest caixer" + "ca": "Pots dipositar diners a aquest caixer", + "cs": "Do tohoto bankomatu můžete vkládat hotovost", + "fr": "Vous pouvez déposer de l'argent liquide dans ce DAB" } }, { @@ -214,7 +235,9 @@ "de": "Sie können an diesem Geldautomaten kein Bargeld einzahlen", "nl": "Je kan geen geld deponeren in deze geldautomaat", "nb_NO": "Du kan ikke gjøre innskudd i denne minibanken", - "ca": "No pots dipositar diners a aquest caixer" + "ca": "No pots dipositar diners a aquest caixer", + "cs": "Do tohoto bankomatu nelze vkládat hotovost", + "fr": "Vous ne pouvez pas déposer d'agent liquide dans ce DAB" } } ] @@ -225,7 +248,8 @@ "en": "Does this ATM have speech output for visually impaired users?", "de": "Verfügt dieser Geldautomat über eine Sprachausgabe für sehbehinderte Benutzer?", "nl": "Heeft deze automaat spraak voor slechtziende en blinde gebruikers?", - "ca": "Aquest caixer té un lector de pantalla per a usuaris amb discapacitat visual?" + "ca": "Aquest caixer té un lector de pantalla per a usuaris amb discapacitat visual?", + "cs": "Má tento bankomat hlasový výstup pro zrakově postižené uživatele?" }, "mappings": [ { @@ -234,7 +258,8 @@ "en": "This ATM has speech output, usually available through a headphone jack", "de": "Dieser Geldautomat verfügt über eine Sprachausgabe, die normalerweise über eine Kopfhörerbuchse verfügbar ist", "nl": "Deze automaat heeft spraak, waarschijnlijk beschikbaar via een hoofdtelefoon-aansluiting", - "ca": "Aquest caixer té lector de pantalla, normalment disponible a través d'un connector d'auriculars \"jack\"" + "ca": "Aquest caixer té lector de pantalla, normalment disponible a través d'un connector d'auriculars \"jack\"", + "cs": "Tento bankomat má řečový výstup, který je obvykle dostupný přes konektor pro sluchátka" } }, { @@ -243,7 +268,8 @@ "en": "This ATM does not have speech output", "de": "Dieser Geldautomat hat keine Sprachausgabe", "nl": "Deze automaat heeft geen spraak", - "ca": "Aquest caixer no té lector de pantalla" + "ca": "Aquest caixer no té lector de pantalla", + "cs": "Tento bankomat nemá hlasový výstup" } } ] @@ -259,19 +285,22 @@ "en": "In which languages does this ATM have speech output?", "de": "In welchen Sprachen hat dieser Geldautomat eine Sprachausgabe?", "nl": "In welke taal is de srpaak van deze geldautomaat?", - "ca": "En quins idiomes té sortida de veu aquest caixer?" + "ca": "En quins idiomes té sortida de veu aquest caixer?", + "cs": "V jakých jazycích má tento bankomat řečový výstup?" }, "render_list_item": { "en": "This ATM has speech output in {language():font-bold}", "de": "Dieser Geldautomat hat eine Sprachausgabe in {language():font-bold}", "nl": "Deze geldautomaat heeft spraak in {language():font-bold}", - "ca": "Aquest caixer té sortida de veu en {language():font-bold}" + "ca": "Aquest caixer té sortida de veu en {language():font-bold}", + "cs": "Tento bankomat má řečový výstup v {language():font-bold}" }, "render_single_language": { "en": "This ATM has speech output in {language():font-bold}", "de": "Dieser Geldautomat hat eine Sprachausgabe in {language():font-bold}", "nl": "Deze automaat heeft spraak in {language():font-bold}", - "ca": "Aquest caixer té sortida de veu en {language():font-bold}" + "ca": "Aquest caixer té sortida de veu en {language():font-bold}", + "cs": "Tento bankomat má řečový výstup v {language():font-bold}" } } } @@ -308,7 +337,8 @@ "en": "With speech output", "de": "Mit Sprachausgabe", "nl": "Heeft spraak", - "ca": "Amb sortida de veu" + "ca": "Amb sortida de veu", + "cs": "S hlasovým výstupem" }, "osmTags": "speech_output=yes" } diff --git a/assets/layers/bank/bank.json b/assets/layers/bank/bank.json index 2f4359f541..64bd96d632 100644 --- a/assets/layers/bank/bank.json +++ b/assets/layers/bank/bank.json @@ -4,14 +4,16 @@ "en": "A financial institution to deposit money", "de": "Ein Finanzinstitut, um Geld einzuzahlen", "nl": "Een financiële instelling waar je geld kunt", - "ca": "Una institució financera per a dipositar diners" + "ca": "Una institució financera per a dipositar diners", + "cs": "Finanční instituce pro ukládání peněz" }, "name": { "en": "Banks", "de": "Banken", "ca": "Bancs", "nb_NO": "Banker", - "nl": "Banken" + "nl": "Banken", + "cs": "Banky" }, "title": { "render": "Bank", @@ -42,7 +44,8 @@ "de": "Hat diese Bank einen Geldautomaten?", "nb_NO": "Har denne banken en minibank?", "nl": "Heeft deze bank een bankautomaat?", - "ca": "Aquest banc té un caixer automàtic?" + "ca": "Aquest banc té un caixer automàtic?", + "cs": "Má tato banka bankomat?" }, "mappings": [ { @@ -52,7 +55,8 @@ "de": "Diese Bank hat einen Geldautomaten", "nb_NO": "Denne banken har en minibank", "nl": "Deze bank heeft een bankautomaat", - "ca": "Aquest banc té un caixer automàtic" + "ca": "Aquest banc té un caixer automàtic", + "cs": "Tato banka má bankomat" } }, { @@ -62,7 +66,8 @@ "de": "Diese Bank hat keinen Geldautomaten", "nb_NO": "Denne banken har ikke en minibank", "nl": "Deze bank heeft geen bankautomaaat", - "ca": "Aquest banc no té un caixer automàtic" + "ca": "Aquest banc no té un caixer automàtic", + "cs": "Tato banka nemá bankomat" } }, { @@ -71,7 +76,8 @@ "en": "This bank does have an ATM, but it is mapped as a different icon", "de": "Diese Bank hat zwar einen Geldautomaten, aber dieser ist mit einem anderen Symbol dargestellt", "nl": "Deze bank heeft een bankautomaat, maar deze staat apart op de kaart aangeduid", - "ca": "Aquest banc té un caixer, però està mapejat com a un element diferent" + "ca": "Aquest banc té un caixer, però està mapejat com a un element diferent", + "cs": "Tato banka má bankomat, ale je namapován jako jiná ikona" } } ] @@ -88,7 +94,8 @@ "de": "Mit Geldautomat", "nb_NO": "Med en minibank", "nl": "Met een bankautomaat", - "ca": "Amb un caixer automàtic" + "ca": "Amb un caixer automàtic", + "cs": "S bankomatem" }, "osmTags": "atm=yes" } diff --git a/assets/layers/barrier/barrier.json b/assets/layers/barrier/barrier.json index b24f89c929..190cb4ab56 100644 --- a/assets/layers/barrier/barrier.json +++ b/assets/layers/barrier/barrier.json @@ -407,7 +407,8 @@ "fr": "Poire, l’espace en hauteur est plus faible qu’au sol", "es": "Barrera de seguridad, el espacio es menor en la parte superior que en la inferior", "da": "Squeeze gate, mellemrummet er mindre i toppen end i bunden", - "ca": "Barrera de seguretat, l'espai és menor a la part superior que a l'inferior" + "ca": "Barrera de seguretat, l'espai és menor a la part superior que a l'inferior", + "cs": "Zúžená brána, mezera nahoře je menší než dole" }, "icon": { "path": "./assets/themes/cycle_infra/Cycle_barrier_squeeze.png", diff --git a/assets/layers/bench/bench.json b/assets/layers/bench/bench.json index ad1e38049c..1a149dada7 100644 --- a/assets/layers/bench/bench.json +++ b/assets/layers/bench/bench.json @@ -75,7 +75,8 @@ "en": "This bench is two-sided and shares the backrest", "nl": "Dit is een dubbele bank waarbij de rugleuning gedeeld wordt", "de": "Diese Bank ist zweiseitig und teilt sich die Rückenlehne", - "ca": "Aquest banc té dues cares i comparteix el respatller" + "ca": "Aquest banc té dues cares i comparteix el respatller", + "cs": "Tato lavička je oboustranná a má společné opěradlo." }, "icon": { "path": "./assets/layers/bench/two_sided.svg", @@ -216,7 +217,8 @@ "de": "Diese Bank hat keine getrennten Sitze", "fr": "Ce banc n'a pas de sièges séparés", "es": "Este banco no tiene asientos separados", - "ca": "Aquest banc no té els seients separats" + "ca": "Aquest banc no té els seients separats", + "cs": "Tato lavička nemá oddělená sedadla" } } ] @@ -757,7 +759,8 @@ "nl": "Vandaag nagekeken!", "de": "Heute geprüft!", "fr": "Vérifié sur le terrain aujourd'hui !", - "ca": "Inspeccionat avui!" + "ca": "Inspeccionat avui!", + "cs": "Zjištěno dnes!" } } ], @@ -776,14 +779,16 @@ "nl": "Deze bank heeft een inscriptie:

{inscription}

", "de": "Diese Bank hat folgende Inschrift:

{inscription}

", "fr": "Ce banc a l'inscription suivante :

{inscription}

", - "ca": "Aquest banc té la següent inscripció:

{inscription}

" + "ca": "Aquest banc té la següent inscripció:

{inscription}

", + "cs": "Tato lavice má následující nápis:

{inscription}

" }, "question": { "en": "Does this bench have an inscription?", "nl": "Heeft deze bank een inscriptie?", "de": "Hat diese Bank eine Inschrift? ", "fr": "Est-ce que ce banc possède une inscription ?", - "ca": "Aquest banc té una inscripció?" + "ca": "Aquest banc té una inscripció?", + "cs": "Má tato lavička nápis?" }, "freeform": { "key": "inscription", @@ -800,7 +805,8 @@ "nl": "Deze bank heeft geen inscriptie", "de": "Diese Bank hat keine Inschrift", "fr": "Ce banc n'a pas d'inscription", - "ca": "Aquest banc no té cap inscripció" + "ca": "Aquest banc no té cap inscripció", + "cs": "Tato lavička nemá nápis" }, "addExtraTags": [ "inscription=" @@ -814,7 +820,8 @@ "de": "Diese Bank hat (wahrscheinlich) keine Inschrift", "fr": "Ce banc n'a(probablement) pas d'inscription", "es": "Este banco (probablemente) no tiene inscripción", - "ca": "Aquest banc (probablement) no té cap inscripció" + "ca": "Aquest banc (probablement) no té cap inscripció", + "cs": "Tato lavička (pravděpodobně) nemá nápis" }, "hideInAnswer": true } @@ -824,7 +831,8 @@ "nl": "Bijvoorbeeld op een aangebracht plakkaat, ingesneden in de rugleuning, ...", "de": "Z.B. auf einer angebrachten Plakette, in der Rückenlehne, ... ", "fr": "Par exemple, sur une plaque accrochée, sur le dossier, ...", - "ca": "P. ex. en una placa, al respatller, ..." + "ca": "P. ex. en una placa, al respatller, ...", + "cs": "Např. na připevněné desce, v opěradle, ..." } }, { @@ -833,7 +841,8 @@ "en": "Does this bench have an artistic element?", "nl": "Heeft deze bank een geïntegreerd kunstwerk?", "de": "Hat diese Bank ein künstlerisches Element? ", - "ca": "Aquest banc té algun element artístic?" + "ca": "Aquest banc té algun element artístic?", + "cs": "Má tato lavička umělecké prvky?" }, "mappings": [ { @@ -843,7 +852,8 @@ "nl": "Deze bank heeft een geïntegreerd kunstwerk", "de": "Diese Bank hat ein integriertes Kunstwerk", "fr": "Une oeuvre d'art est intégrée à ce banc", - "ca": "Aquest banc té integrada una obra d'art" + "ca": "Aquest banc té integrada una obra d'art", + "cs": "Tato lavička má integrované umělecké dílo" } }, { @@ -854,7 +864,8 @@ "de": "Diese Bank hat kein integriertes Kunstwerk", "fr": "Ce banc n'a pas d'oeuvre d'art intégrée", "es": "Este banco no tiene una obra de arte integrada", - "ca": "Aquest banc no té una obra d'art integrada" + "ca": "Aquest banc no té una obra d'art integrada", + "cs": "Tato lavička nemá integrované umělecké dílo" } } ], @@ -862,7 +873,8 @@ "en": "E.g. it has an integrated painting, statue or other non-trivial, creative work", "nl": "Bijvoorbeeld een standbeeld, schildering of ander, niet-triviaal kunstwerk", "de": "Z.B. hat es ein integriertes Gemälde, eine Statue oder eine andere nicht triviale, kreative Arbeit", - "ca": "P.e. té una pintura integrada, estatua o altres treballs no trivials i creatius" + "ca": "P.e. té una pintura integrada, estatua o altres treballs no trivials i creatius", + "cs": "Např. má integrovaný obraz, sochu nebo jiné netriviální tvůrčí dílo" } }, { @@ -883,7 +895,8 @@ "nl": "Is deze bank een gedenkteken voor iemand of iets?", "de": "Dient diese Bank als Denkmal für jemanden oder etwas?", "fr": "Ce banc sert-il de mémorial pour quelqu'un ou quelque chose ?", - "ca": "Aquest banc actua com a memorial per a algú o algo?" + "ca": "Aquest banc actua com a memorial per a algú o algo?", + "cs": "Slouží tato lavička jako památník někoho nebo něčeho?" }, "mappings": [ { @@ -893,7 +906,8 @@ "nl": "Deze bank is een gedenkteken aan iemand of iets", "de": "Diese Bank ist ein Denkmal für jemanden oder etwas", "fr": "Ce banc est un mémorial pour quelqu'un ou quelque chose", - "ca": "Aquest banc és un memorial per a algú o alguna cosa" + "ca": "Aquest banc és un memorial per a algú o alguna cosa", + "cs": "Tato lavička je pomníkem pro někoho nebo něco" }, "addExtraTags": [ "memorial=bench" @@ -911,7 +925,8 @@ "nl": "Deze bank is geen gedenkteken aan iemand of iets", "de": "Diese Bank ist kein Denkmal für jemanden oder etwas", "fr": "Ce banc n'est pas un mémorial pour quelqu'un ou quelque chose", - "ca": "Aquest banc no és un memorial per a algú o alguna cosa" + "ca": "Aquest banc no és un memorial per a algú o alguna cosa", + "cs": "Tato lavička není pro někoho nebo něco památníkem" }, "addExtraTags": [ "memorial=" @@ -1005,7 +1020,8 @@ "nl": "is een gedenkteken", "de": "ist ein Denkmal", "fr": "est un mémorial", - "ca": "és un memorial" + "ca": "és un memorial", + "cs": "je památník" } } ] @@ -1019,7 +1035,8 @@ "nl": "Met en zonder rugleuning", "de": "Mit und ohne Rückenlehne", "fr": "Avec et sans dossier", - "ca": "Amb i sense respatller" + "ca": "Amb i sense respatller", + "cs": "S opěradlem a bez opěradla" } }, { @@ -1029,7 +1046,8 @@ "nl": "Heeft een rugleuning", "de": "Mit Rückenlehne", "fr": "A un dossier", - "ca": "Té un respatller" + "ca": "Té un respatller", + "cs": "Má opěradlo" } }, { @@ -1039,7 +1057,8 @@ "nl": "Heeft geen rugleuning", "de": "Ohne Rückenlehne", "fr": "N'a pas de dossier", - "ca": "No té respatller" + "ca": "No té respatller", + "cs": "Nemá opěradlo" } } ] diff --git a/assets/layers/bench_at_pt/bench_at_pt.json b/assets/layers/bench_at_pt/bench_at_pt.json index 61d602c4b9..801b7a50cc 100644 --- a/assets/layers/bench_at_pt/bench_at_pt.json +++ b/assets/layers/bench_at_pt/bench_at_pt.json @@ -104,7 +104,8 @@ "pt": "Banco em abrigo", "es": "Banco en marquesina", "da": "Bænk i læskur", - "cs": "Lavička v přístřešku" + "cs": "Lavička v přístřešku", + "ca": "Banc en marquesina" } } ] @@ -230,7 +231,8 @@ "de": "Diese Bushaltestelle hat keine Bank (es gab nie eine oder sie wurde entfernt)", "fr": "Cette station de bus n'a pas de banc (il n'y en a jamais eu ou il a été retiré)", "nl": "Deze bushalte heeft geen zitbank (er is er nooit een geweest of deze is verwijderd)", - "ca": "Aquesta para de bus no té un banc (mai n'ha tingut un o ha estat eliminat)" + "ca": "Aquesta para de bus no té un banc (mai n'ha tingut un o ha estat eliminat)", + "cs": "Na této autobusové zastávce není lavička (nikdy zde nebyla nebo byla odstraněna)" } } ], @@ -242,7 +244,8 @@ "de": "Diese Bushaltestelle wird nicht mehr genutzt", "fr": "Cette station de bus n'est plus utilisée", "nl": "Deze bushalte wordt niet meer gebruikt", - "ca": "Aquesta parada de bus no s'utilitza més" + "ca": "Aquesta parada de bus no s'utilitza més", + "cs": "Tato autobusová zastávka se již nepoužívá" } } ], diff --git a/assets/layers/bicycle_rental/bicycle_rental.json b/assets/layers/bicycle_rental/bicycle_rental.json index 983a4e27ed..75e1f77807 100644 --- a/assets/layers/bicycle_rental/bicycle_rental.json +++ b/assets/layers/bicycle_rental/bicycle_rental.json @@ -457,7 +457,8 @@ "de": "Wie viele type_plural können hier gemietet werden?", "fr": "Combien de type_plural peuvent être loués ici ?", "cs": "Kolik typů kol si zde můžete pronajmout?", - "es": "¿Cuántas type_plural pueden alquilarse aquí?" + "es": "¿Cuántas type_plural pueden alquilarse aquí?", + "ca": "Quantes type_plural poden llogar-se aquí?" }, "render": { "en": "{capacity:bicycle_type} type_plural can be rented here", @@ -465,7 +466,8 @@ "de": "{capacity:bicycle_type} type_plural können hier gemietet werden", "fr": "{capacity:bicycle_type} type_plural peuvent être loués ici", "cs": "{capacity:bicycle_type} typů si můžete pronajmout zde", - "es": "{capacity:bicycle_type} type_plural pueden alquilarse aquí" + "es": "{capacity:bicycle_type} type_plural pueden alquilarse aquí", + "ca": "{capacity:bicycle_type} type_plural es poden llogar aquí" }, "freeform": { "key": "capacity:bicycle_type", diff --git a/assets/layers/bike_cafe/bike_cafe.json b/assets/layers/bike_cafe/bike_cafe.json index bd76d13868..bc301e8a97 100644 --- a/assets/layers/bike_cafe/bike_cafe.json +++ b/assets/layers/bike_cafe/bike_cafe.json @@ -74,7 +74,8 @@ "pt_BR": "Café de bicicleta {name}", "pt": "Café de bicicleta {name}", "da": "Cykelcafé {name}", - "cs": "Cyklokavárna {name}" + "cs": "Cyklokavárna {name}", + "ca": "Cafè ciclista {name}" } } ] @@ -207,7 +208,8 @@ "pt_BR": "Este café de bicicleta oferece ferramentas de reparo faça você mesmo", "pt": "Este café de bicicleta oferece ferramentas de reparo faça você mesmo", "da": "Denne cykelcafé tilbyder værktøj til gør-det-selv-reparation", - "cs": "Tato cyklokavárna nabízí nářadí pro kutilské opravy" + "cs": "Tato cyklokavárna nabízí nářadí pro kutilské opravy", + "ca": "Aquest cafè ciclista ofereix ferramentes per a la reparació DIY" } }, { @@ -225,7 +227,8 @@ "pt_BR": "Este café de bicicleta não oferece ferramentas de reparo faça você mesmo", "pt": "Este café de bicicleta não oferece ferramentas de reparo faça você mesmo", "da": "Denne cykelcafé tilbyder ikke værktøj til gør-det-selv-reparation", - "cs": "Tato cyklokavárna nenabízí nářadí pro kutilské opravy" + "cs": "Tato cyklokavárna nenabízí nářadí pro kutilské opravy", + "ca": "Aquest cafè ciclista no ofereix ferramentes per a la reparació DIY" } } ] diff --git a/assets/layers/bike_cleaning/bike_cleaning.json b/assets/layers/bike_cleaning/bike_cleaning.json index dc91c66696..c7f96d15fd 100644 --- a/assets/layers/bike_cleaning/bike_cleaning.json +++ b/assets/layers/bike_cleaning/bike_cleaning.json @@ -152,7 +152,7 @@ "fr": "Utilisation gratuite", "da": "Gratis at bruge", "cs": "Bezplatné používání", - "ca": "Debades" + "ca": "Gratuït" }, "hideInAnswer": true } @@ -216,7 +216,8 @@ "nl": "Dit fietsschoonmaakpunt is betalend", "es": "Este servicio de limpieza es de pago", "ca": "Aquest servei de neteja és de pagament", - "de": "Dieser Reinigungsservice ist kostenpflichtig" + "de": "Dieser Reinigungsservice ist kostenpflichtig", + "cs": "Tato úklidová služba je placená" } } ], diff --git a/assets/layers/bike_repair_station/bike_repair_station.json b/assets/layers/bike_repair_station/bike_repair_station.json index f1605083d3..346f73b248 100644 --- a/assets/layers/bike_repair_station/bike_repair_station.json +++ b/assets/layers/bike_repair_station/bike_repair_station.json @@ -34,7 +34,8 @@ "ru": "Велостанция (накачка шин и ремонт)", "es": "Estación de bicis (bomba y reparación)", "da": "Cykelstation (pumpe og reparation)", - "ca": "Estació de bicicletes (bomba i reparació)" + "ca": "Estació de bicicletes (bomba i reparació)", + "cs": "Cyklistická stanice (pumpa a opravna)" }, "mappings": [ { @@ -887,7 +888,8 @@ "pt": "Um aparelho para encher os seus pneus num local fixa no espaço público", "es": "Un dispositivo para inflar tus ruedas en una posición fija en el espacio público.", "da": "En anordning til at fylde dine dæk op på et fast sted i det offentlige rum.", - "cs": "Zařízení pro huštění pneumatik na pevném místě na veřejném místě." + "cs": "Zařízení pro huštění pneumatik na pevném místě na veřejném místě.", + "ca": "Un dispositiu per a unflar les teues rodes en una posició fixa a l'espai públic." }, "exampleImages": [ "./assets/layers/bike_repair_station/pump_example_round.jpg", diff --git a/assets/layers/bike_shop/bike_shop.json b/assets/layers/bike_shop/bike_shop.json index c9eae8908a..7f6c2ca61e 100644 --- a/assets/layers/bike_shop/bike_shop.json +++ b/assets/layers/bike_shop/bike_shop.json @@ -12,7 +12,8 @@ "pt": "Reparo/loja de bicicletas", "ca": "Botiga/reparació de bicicletes", "es": "Taller/tienda de bicis", - "da": "Cykelreparation/butik" + "da": "Cykelreparation/butik", + "cs": "Opravna/obchod s jízdními koly" }, "minzoom": 13, "allowMove": true, @@ -138,7 +139,8 @@ "pt_BR": "Reparo de bicicletas {name}", "pt": "Reparo de bicicletas {name}", "es": "Reparación de bicis {name}", - "da": "Cykelreparation {name}" + "da": "Cykelreparation {name}", + "ca": "Reparació de bicis {name}" } }, { @@ -158,7 +160,8 @@ "pt_BR": "Loja de bicicletas {name}", "pt": "Loja de bicicletas {name}", "es": "Tienda de bicis {name}", - "da": "Cykelforretning {name}" + "da": "Cykelforretning {name}", + "ca": "Botiga de bicis {name}" } }, { @@ -173,7 +176,8 @@ "pt_BR": "Loja/reparo de bicicletas {name}", "pt": "Loja/reparo de bicicletas {name}", "da": "Cykelværksted{name}", - "es": "Taller/tienda de bicis {name}" + "es": "Taller/tienda de bicis {name}", + "ca": "Taller/botiga de bicis {name}" } } ] @@ -213,7 +217,8 @@ "pt": "Uma loja que vende especificamente bicicletas ou itens relacionados", "es": "Una tiene que vende específicamente bicis u objetos relacionados", "da": "En butik, der specifikt sælger cykler eller relaterede varer", - "ca": "Una botiga que ven específicament bicicletes o articles relacionats" + "ca": "Una botiga que ven específicament bicicletes o articles relacionats", + "cs": "Obchod zaměřený na prodej jízdních kol nebo souvisejících předmětů" }, "tagRenderings": [ "images", @@ -478,7 +483,8 @@ "pt": "Esta loja aluga bicicletas", "es": "Esta tienda alquila bicis", "da": "Denne butik udlejer cykler", - "ca": "Aquesta botiga lloga bicis" + "ca": "Aquesta botiga lloga bicis", + "cs": "Tento obchod pronajímá jízdní kola" } }, { @@ -584,7 +590,8 @@ "ru": "Предлагается ли в этом магазине велосипедный насос для всеобщего пользования?", "es": "¿Esta tienda ofrece una bomba para que la utilice cualquiera?", "da": "Tilbyder denne butik en cykelpumpe til brug for alle?", - "ca": "Aquesta botiga ofereix una manxa perquè la utilitzi qualsevol?" + "ca": "Aquesta botiga ofereix una manxa perquè la utilitzi qualsevol?", + "cs": "Nabízí tento obchod pumpu na kolo k použití pro kohokoli?" }, "mappings": [ { @@ -599,7 +606,8 @@ "ru": "В этом магазине есть велосипедный насос для всеобщего пользования", "es": "Esta tienda ofrece una bomba para cualquiera", "da": "Denne butik tilbyder en cykelpumpe til alle", - "ca": "Aquesta botiga ofereix una manxa per a tothom" + "ca": "Aquesta botiga ofereix una manxa per a tothom", + "cs": "Tento obchod nabízí pumpu na kolo pro každého" } }, { @@ -614,7 +622,8 @@ "ru": "В этом магазине нет велосипедного насоса для всеобщего пользования", "es": "Esta tienda no ofrece una bomba para cualquiera", "da": "Denne butik tilbyder ikke en cykelpumpe til nogen", - "ca": "Aquesta botiga no ofereix una manxa per a tothom" + "ca": "Aquesta botiga no ofereix una manxa per a tothom", + "cs": "Tento obchod nenabízí pumpičku na kolo pro každého" } }, { @@ -627,7 +636,8 @@ "de": "Es gibt eine Luftpumpe, sie ist als separater Punkt eingetragen", "es": "Hay una bomba para bicicletas, se muestra como un punto separado", "da": "Der er cykelpumpe, den er vist som et separat punkt", - "ca": "Hi ha una manxa, es mostra com a un punt separat" + "ca": "Hi ha una manxa, es mostra com a un punt separat", + "cs": "K dispozici je pumpa na jízdní kola, je zobrazena jako samostatný bod" } } ] @@ -702,7 +712,8 @@ "de": "Bietet das Geschäft Fahrradreinigungen an?", "es": "¿Aquí se lavan bicicletas?", "da": "Vaskes cykler her?", - "ca": "Aquí es renten bicicletes?" + "ca": "Aquí es renten bicicletes?", + "cs": "Myjí se zde jízdní kola?" }, "mappings": [ { @@ -716,7 +727,8 @@ "ru": "В этом магазине оказываются услуги мойки/чистки велосипедов", "es": "Esta tienda limpia bicicletas", "da": "Denne butik rengør cykler", - "ca": "Aquesta botiga renta bicicletes" + "ca": "Aquesta botiga renta bicicletes", + "cs": "Tento obchod čistí jízdní kola" } }, { @@ -729,7 +741,8 @@ "de": "Im Geschäft können Fahrräder selbst gereinigt werden", "es": "Esta tienda tiene una instalación donde uno puede limpiar bicicletas por si mismo", "da": "Denne butik har et anlæg, hvor man selv kan rengøre cykler", - "ca": "Aquesta botiga té una instal·lació on un pot rentar les bicis per un mateix" + "ca": "Aquesta botiga té una instal·lació on un pot rentar les bicis per un mateix", + "cs": "Tento obchod má zařízení, kde si můžete sami vyčistit jízdní kola" } }, { @@ -743,7 +756,8 @@ "ru": "В этом магазине нет услуг мойки/чистки велосипедов", "es": "Esta tienda no ofrece limpieza de bicicletas", "da": "Denne butik tilbyder ikke rengøring af cykler", - "ca": "Aquesta botiga no ofereix rentat de bicis" + "ca": "Aquesta botiga no ofereix rentat de bicis", + "cs": "Tento obchod nenabízí čištění jízdních kol" } } ] @@ -763,7 +777,8 @@ "ru": "Обслуживание велосипедов/магазин", "es": "un taller/tienda de bicis", "da": "en cykelværksted/butik", - "ca": "una botiga/reparació de bicicletes" + "ca": "una botiga/reparació de bicicletes", + "cs": "opravna/obchod s jízdními koly" }, "tags": [ "shop=bicycle" diff --git a/assets/layers/bike_themed_object/bike_themed_object.json b/assets/layers/bike_themed_object/bike_themed_object.json index b9d61cf7fe..856f0f8bad 100644 --- a/assets/layers/bike_themed_object/bike_themed_object.json +++ b/assets/layers/bike_themed_object/bike_themed_object.json @@ -34,7 +34,8 @@ "de": "Mit Fahrrad zusammenhängendes Objekt", "it": "Oggetto relativo alle bici", "es": "Objeto relacionado con bicis", - "da": "Cykelrelateret objekt" + "da": "Cykelrelateret objekt", + "ca": "Objecte relacionat amb bicis" }, "mappings": [ { @@ -94,6 +95,7 @@ "de": "Eine Ebene mit Objekten zum Thema Fahrrad, die zu keiner anderen Ebene passen", "es": "Una capa con los objetos relacionados con bicis pero que no coinciden con ninguna otra capa", "fr": "Une couche sur le thème des vélos mais qui ne correspondent à aucune autre couche", - "da": "Et lag med objekter med cykeltema, men som ikke matcher noget andet lag" + "da": "Et lag med objekter med cykeltema, men som ikke matcher noget andet lag", + "ca": "Una capa amb els objectes relacionats amb bicis però que no coinxideixen amb cap altra capa" } } \ No newline at end of file diff --git a/assets/layers/binocular/binocular.json b/assets/layers/binocular/binocular.json index 749766c450..d0b3dee6f8 100644 --- a/assets/layers/binocular/binocular.json +++ b/assets/layers/binocular/binocular.json @@ -54,7 +54,7 @@ "da": "Gratis at bruge", "es": "De uso gratuito", "fr": "En libre service", - "ca": "Debades" + "ca": "Gratuït" } } ], @@ -131,7 +131,8 @@ "de": "Ein fest installiertes Teleskop oder Fernglas, für die öffentliche Nutzung. ", "fr": "Une longue-vue ou une paire de jumelles montée sur un poteau, disponible au public pour scruter les environs. ", "da": "Et teleskop eller en kikkert monteret på en stang, som offentligheden kan se sig omkring med. ", - "es": "Un telescopio o unos prismáticos montados en un poste, disponible para que el público mire alrededor. " + "es": "Un telescopio o unos prismáticos montados en un poste, disponible para que el público mire alrededor. ", + "ca": "Un telescopi o un parell de prismàtics muntats en un pal, a disposició del públic per mirar al seu voltant. " }, "preciseInput": { "preferredBackground": "photo" diff --git a/assets/layers/birdhide/birdhide.json b/assets/layers/birdhide/birdhide.json index ccc9a5b4ea..c7d2249254 100644 --- a/assets/layers/birdhide/birdhide.json +++ b/assets/layers/birdhide/birdhide.json @@ -6,7 +6,8 @@ "de": "Orte zur Vogelbeobachtung", "es": "Lugares para ver pájaros", "da": "Steder til fugleobservation", - "fr": "Lieu pour observer des oiseaux" + "fr": "Lieu pour observer des oiseaux", + "ca": "Llocs per a vore ocells" }, "minzoom": 14, "source": { @@ -77,7 +78,8 @@ "nl": "Een vogelkijkhut", "da": "Et fugleskjul", "de": "Ein Vogelbeobachtungsturm", - "fr": "Un observatoire ornithologique" + "fr": "Un observatoire ornithologique", + "ca": "Un observatori d'ocells" }, "tagRenderings": [ "images", @@ -120,7 +122,8 @@ "nl": "Vogelkijkhut", "da": "Fugleskjul", "de": "Vogelbeobachtungsturm", - "fr": "Observatoire ornithologique" + "fr": "Observatoire ornithologique", + "ca": "Observatori d'ocells" } }, { @@ -286,7 +289,8 @@ "nl": "een vogelkijkhut", "da": "et fugleskjul", "de": "ein Gebäude zur Vogelbeobachtung", - "fr": "un observatoire ornithologique" + "fr": "un observatoire ornithologique", + "ca": "un observatori d'ocells" }, "description": { "en": "A covered shelter where one can watch birds comfortably", @@ -294,7 +298,8 @@ "de": "Ein überdachter Unterstand, in dem man bequem Vögel beobachten kann", "es": "Un refugio cubierto donde se pueden ver pájaros confortablemente", "da": "Et overdækket ly, hvor man kan se fugle i ro og mag", - "fr": "Un abris couvert pour observer les oiseaux confortablement" + "fr": "Un abris couvert pour observer les oiseaux confortablement", + "ca": "Un refugi cobert on es poden veure ocells confortablement" } }, { @@ -316,7 +321,8 @@ "es": "Una pantalla o pared con aperturas para ver pájaros", "da": "En skærm eller væg med åbninger til at se på fugle", "de": "Ein Schirm oder eine Wand mit Öffnungen zum Beobachten von Vögeln", - "fr": "Un écran ou un mur avec des ouvertures pour observer les oiseaux" + "fr": "Un écran ou un mur avec des ouvertures pour observer les oiseaux", + "ca": "Una pantalla o paret amb obertures per a observar ocells" } } ], @@ -353,7 +359,8 @@ "nl": "Enkel overdekte kijkhutten", "de": "Nur überdachte Vogelbeobachtungsstellen", "da": "Kun overdækkede fugleskjul", - "fr": "Seulement les observatoires ornithologiques couverts" + "fr": "Seulement les observatoires ornithologiques couverts", + "ca": "Només observatoris d'ocells coberts" }, "osmTags": { "and": [ diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 89bf9bf748..56319e5ea8 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -289,7 +289,8 @@ "nl": "Schuko stekker zonder aardingspin (CEE7/4 type F)", "da": "Schuko vægstik uden jordstift (CEE7/4 type F)", "de": "Schuko-Stecker ohne Erdungsstift (CEE7/4 Typ F)", - "es": "Enchufe de pared Schuko sin pin de tierra (CEE7/4 tipo F)" + "es": "Enchufe de pared Schuko sin pin de tierra (CEE7/4 tipo F)", + "ca": "Endoll de paret Schuko sense pin a terra (CEE7/4 tipus F)" }, "icon": { "path": "./assets/layers/charging_station/CEE7_4F.svg", @@ -320,7 +321,8 @@ "nl": "Schuko stekker zonder aardingspin (CEE7/4 type F)", "da": "Schuko vægstik uden jordstift (CEE7/4 type F)", "de": "Schuko-Stecker ohne Erdungsstift (CEE7/4 Typ F)", - "es": "Enchufe de pared Schuko sin pin de tierra (CEE7/4 tipo F)" + "es": "Enchufe de pared Schuko sin pin de tierra (CEE7/4 tipo F)", + "ca": "Endoll de paret Schuko sense pin a terra (CEE7/4 tipus F)" }, "hideInAnswer": true, "icon": { @@ -860,7 +862,8 @@ "nl": "Type 2 met kabel (J1772)", "da": "Type 2 med kabel (mennekes)", "de": "Typ 2 mit Kabel (mennekes)", - "es": "Tipo 2 con cable (mennekes)" + "es": "Tipo 2 con cable (mennekes)", + "ca": "Tipus 2 amb cable (mennekes)" }, "hideInAnswer": true, "icon": { @@ -922,7 +925,8 @@ "nl": "Tesla Supercharger CCS (een type2 CCS met Tesla-logo)", "da": "Tesla Supercharger CCS (en mærkevare type2_css)", "de": "Tesla Supercharger CCS (ein Markenzeichen von type2_css)", - "es": "CCS Supercargador Tesla (un tipo2_css con marca)" + "es": "CCS Supercargador Tesla (un tipo2_css con marca)", + "ca": "CSS Supercarregador Tesla (un tipus2_css de la marca)" }, "hideInAnswer": true, "icon": { @@ -938,7 +942,8 @@ "nl": "Tesla Supercharger (destination)", "da": " Tesla Supercharger (destination)", "de": "Tesla Supercharger (Destination)", - "es": "Supercargador Tesla (destino" + "es": "Supercargador Tesla (destino)", + "ca": "Supercarregador Tesla (destí)" }, "icon": { "path": "./assets/layers/charging_station/Tesla-hpwc-model-s.svg", @@ -989,7 +994,8 @@ "nl": "Tesla Supercharger (destination)", "da": " Tesla Supercharger (destination)", "de": "Tesla Supercharger (Destination)", - "es": "Supercargador Tesla (destino)" + "es": "Supercargador Tesla (destino)", + "ca": "Supercarregador Tesla (destí)" }, "hideInAnswer": true, "icon": { @@ -1391,12 +1397,14 @@ "question": { "en": "How much plugs of type
Type 2 (mennekes)
are available here?", "nl": "Hoeveel stekkers van type
Type 2 (mennekes)
heeft dit oplaadpunt?", - "de": "Wie viele Stecker des Typs
Typ 2 (Mennekes)
sind hier vorhanden?" + "de": "Wie viele Stecker des Typs
Typ 2 (Mennekes)
sind hier vorhanden?", + "ca": "Quants endolls del tipus
Tipus 2 (mennekes)
hi ha disponibles aquí?" }, "render": { "en": "There are {socket:type2} plugs of type
Type 2 (mennekes)
available here", "nl": "Hier zijn {socket:type2} stekkers van het type
Type 2 (mennekes)
", - "de": "Hier sind {socket:type2} Stecker des Typs
Typ 2 (Mennekes)
vorhanden" + "de": "Hier sind {socket:type2} Stecker des Typs
Typ 2 (Mennekes)
vorhanden", + "ca": "Hi ha {socket:type2} endolls del tipus
Tipus 2 (mennekes)
disponibles aquí" }, "freeform": { "key": "socket:type2", @@ -3636,14 +3644,16 @@ "nl": "Welke stroom levert de stekker van type
USB om GSMs en kleine electronica op te laden
?", "da": "Hvilken strømstyrke har stikkene med
USB til opladning af telefoner og småt elektronikudstyr
?", "de": "Welche Stromstärke liefern die Stecker mit
USB zum Laden von Handys und kleinen Elektrogeräten
?", - "es": "¿Qué corriente ofrecen los conectores con
USB para cargar teléfonos y dispositivos electrónicos pequeños
?" + "es": "¿Qué corriente ofrecen los conectores con
USB para cargar teléfonos y dispositivos electrónicos pequeños
?", + "ca": "Quina corrent ofereixen els connectors amb
USBper a carrega telèfons i dispositius electrònics petits
?" }, "render": { "en": "
USB to charge phones and small electronics
outputs at most {socket:USB-A:current}A", "nl": "
USB om GSMs en kleine electronica op te laden
levert een stroom van maximaal {socket:USB-A:current}A", "da": "
USB til opladning af telefoner og småt elektronikudstyr
udsender højst {socket:USB-A:current}A", "de": "
USB zum Aufladen von Telefonen und kleinen Elektrogeräten
liefert maximal {socket:USB-A:current} A", - "es": "
USB para carga teléfonos y dispositivos electrónicos pequeños
salida de hasta {socket:USB-A:current}A" + "es": "
USB para carga teléfonos y dispositivos electrónicos pequeños
salida de hasta {socket:USB-A:current}A", + "ca": "
USBper a carregar telèfons i petits dispositius electrònics\ncom a màxim a {socket:USB-A:current}A" }, "freeform": { "key": "socket:USB-A:current", @@ -3657,7 +3667,8 @@ "nl": "USB om GSMs en kleine electronica op te laden levert een stroom van maximaal 1 A", "da": "USB til opladning af telefoner og mindre elektronik yder højst 1 A", "de": "USB zum Laden von Handys und kleinen Elektrogeräten liefert maximal 1 A", - "es": "USB para cargar teléfonos y dispositivos electrónicos pequeños hasta 1 A" + "es": "USB para cargar teléfonos y dispositivos electrónicos pequeños hasta 1 A", + "ca": "USB per a carregar telèfons i dispositius petits fins a 1 A" }, "icon": { "path": "./assets/layers/charging_station/usb_port.svg", @@ -3671,7 +3682,8 @@ "nl": "USB om GSMs en kleine electronica op te laden levert een stroom van maximaal 2 A", "da": "USB til opladning af telefoner og små elektroniske apparater yder højst 2 A", "de": "USB zum Laden von Handys und kleinen Elektrogeräten liefert maximal 2 A", - "es": "USB para cargar teléfonos y dispositivos electrónicos pequeños hasta 1 A" + "es": "USB para cargar teléfonos y dispositivos electrónicos pequeños hasta 1 A", + "ca": "USB per a carregar telèfons i dispositius petits fins a 2 A" }, "icon": { "path": "./assets/layers/charging_station/usb_port.svg", @@ -4052,7 +4064,8 @@ "da": "Hvilken form for godkendelse er tilgængelig ved ladestationen?", "de": "Welche Art der Authentifizierung ist an der Ladestation möglich?", "es": "¿Qué tipo de autenticación está disponible en esta estación de carga?", - "fr": "Quelle sorte d'authentification est disponible à cette station de charge ?" + "fr": "Quelle sorte d'authentification est disponible à cette station de charge ?", + "ca": "Quin tipus d'autenticació hi ha disponible a l'estació de càrrega?" }, "multiAnswer": true, "mappings": [ @@ -4065,7 +4078,8 @@ "da": "Godkendelse med et medlemskort", "de": "Authentifizierung per Mitgliedskarte", "es": "Autenticación mediante tarjeta de membresía", - "fr": "Authentification par carte de membre" + "fr": "Authentification par carte de membre", + "ca": "Autenticació mitjançant una targeta de soci" } }, { @@ -4077,7 +4091,8 @@ "da": "Godkendelse med en app", "de": "Authentifizierung per App", "es": "Autenticación mediante aplicación", - "fr": "Authentification par une app" + "fr": "Authentification par une app", + "ca": "Autenticació mitjançant una aplicació" } }, { @@ -4089,7 +4104,8 @@ "da": "Godkendelse via telefonopkald er tilgængelig", "de": "Authentifizierung per Anruf ist möglich", "es": "Autenticación mediante llamada telefónica disponible", - "fr": "Authentification par appel téléphonique est disponible" + "fr": "Authentification par appel téléphonique est disponible", + "ca": "L'autenticació per trucada telefònica està disponible" } }, { @@ -4101,7 +4117,8 @@ "da": "Godkendelse via SMS er tilgængelig", "de": "Authentifizierung per SMS ist möglich", "es": "Autenticación mediante SMS disponible", - "fr": "Authentification par SMS est disponible" + "fr": "Authentification par SMS est disponible", + "ca": "L'autenticació per SMS està disponible" } }, { @@ -4113,7 +4130,8 @@ "da": "Godkendelse via NFC er tilgængelig", "de": "Authentifizierung per NFC ist möglich", "es": "Autenticación mediante NFC disponible", - "fr": "Authentification par NFC est disponible" + "fr": "Authentification par NFC est disponible", + "ca": "L'autenticació via NFC està disponible" } }, { @@ -4148,7 +4166,8 @@ "da": "Opladning her er (også) muligt uden godkendelse", "de": "Das Laden ist hier (auch) ohne Authentifizierung möglich", "es": "La carga aquí (también) es posible sin autenticación", - "fr": "Charger ici est (aussi) possible sans authentification" + "fr": "Charger ici est (aussi) possible sans authentification", + "ca": "Carregar aquí (també) és possible sense autenticació" } } ], @@ -4165,14 +4184,16 @@ "en": "Authenticate by calling or SMS'ing to {authentication:phone_call:number}", "nl": "Aanmelden door te bellen of te SMS'en naar {authentication:phone_call:number}", "da": "Godkend dig ved at ringe eller sende en sms til {authentication:phone_call:number}", - "de": "Authentifizierung durch Anruf oder SMS an {authentication:phone_call:number}" + "de": "Authentifizierung durch Anruf oder SMS an {authentication:phone_call:number}", + "ca": "Autentiqueu-vos trucant o enviant SMS a {authentication:phone_call:number}" }, "question": { "en": "What's the phone number for authentication call or SMS?", "nl": "Wat is het telefoonnummer dat men moet bellen of SMS'en om zich aan te melden?", "da": "Hvad er telefonnummeret til godkendelsesopkald eller SMS?", "de": "Wie lautet die Telefonnummer für den Authentifizierungsanruf oder die SMS?", - "es": "¿Cual es el número de teléfono para la llamada de autenticación o SMS?" + "es": "¿Cual es el número de teléfono para la llamada de autenticación o SMS?", + "ca": "Quin és el número de telèfon per a la trucada d'autenticació o SMS?" }, "freeform": { "key": "authentication:phone_call:number", @@ -4203,7 +4224,8 @@ "nl": "De maximale parkeertijd hier is {canonical(maxstay)}", "da": "Man kan højst blive {canonical(maxstay)}", "de": "Die maximale Parkdauer beträgt {canonical(maxstay)}", - "es": "Se puede estar como máximo {canonical(maxstay)}" + "es": "Se puede estar como máximo {canonical(maxstay)}", + "ca": "Un pot quedar-se com a màxim {canonical(maxstay)}" }, "mappings": [ { @@ -4405,7 +4427,8 @@ "question": { "en": "What is the website where one can find more information about this charging station?", "nl": "Wat is de website waar men meer info kan vinden over dit oplaadpunt?", - "de": "Auf welcher Webseite kann man weitere Informationen über diese Ladestation finden?" + "de": "Auf welcher Webseite kann man weitere Informationen über diese Ladestation finden?", + "ca": "Quina és la pàgina web on es pot trobar més informació sobre aquest punt de recàrrega?" }, "render": { "en": "More info on {website}", @@ -4445,7 +4468,8 @@ "nl": "Is dit oplaadpunt operationeel?", "da": "Er denne ladestander i brug?", "de": "Ist die Station in Betrieb?", - "es": "¿Está en uso este punto de carga?" + "es": "¿Está en uso este punto de carga?", + "ca": "Està en ús aquest punt de recàrrega?" }, "mappings": [ { @@ -4520,7 +4544,8 @@ "nl": "Hier wordt op dit moment een oplaadpunt gebouwd", "da": "Her er opført en ladestation", "de": "Die Station ist aktuell im Bau", - "es": "Aquí está construida una estación de carga" + "es": "Aquí se está construyendo una estación de carga", + "ca": "Aquí està construint-se una estació de càrrega" } }, { @@ -4538,7 +4563,8 @@ "nl": "Dit oplaadpunt is niet meer in gebruik maar is wel nog aanwezig", "da": "Denne ladestation er blevet permanent deaktiveret og er ikke længere i brug, men er stadig synlig", "de": "Die Station ist dauerhaft geschlossen und nicht mehr in Nutzung, aber noch sichtbar", - "es": "Esta estación de carga se ha deshabilitado de forma permanente y ya no está en uso pero todavía es visible" + "es": "Esta estación de carga se ha deshabilitado de forma permanente y ya no está en uso pero todavía es visible", + "ca": "Aquesta estació de recàrrega s'ha desactivat permanentment i ja no s'utilitza, però encara és visible" } } ] @@ -4677,7 +4703,8 @@ "nl": "een oplaadpunt voor elektrische fietsen met een gewoon Europees stopcontact (speciaal bedoeld voor fietsen)", "da": "en ladestation til elektriske cykler med et normalt europæisk vægstik (beregnet til opladning af elektriske cykler)", "de": "eine Ladestation für Elektrofahrräder mit einer normalen europäischen Steckdose (zum Laden von Elektrofahrrädern)", - "es": "una estación de carga para bicicletas eléctricas con un enchufe de pared europeo normal (pensado para cargar bicicletas eléctricas)" + "es": "una estación de carga para bicicletas eléctricas con un enchufe de pared europeo normal (pensado para cargar bicicletas eléctricas)", + "ca": "una estació de càrrega per a bicicletes elèctriques amb un endoll de paret europeu normal (destinat a carregar bicicletes elèctriques)" }, "preciseInput": { "preferredBackground": "map" @@ -4694,7 +4721,8 @@ "nl": "een oplaadstation voor elektrische auto's", "da": "en ladestation til biler", "de": "Eine Ladestation für Elektrofahrzeuge", - "es": "una estación de carga para coches" + "es": "una estación de carga para coches", + "ca": "una estació de càrrega per a cotxes" }, "preciseInput": { "preferredBackground": "map" @@ -4820,7 +4848,8 @@ "nl": "Heeft een
Type 1 met kabel (J1772)
", "da": "Har et
Type 1 med kabel (J1772)
stik", "de": "Verfügt über einen
Typ 1 (J1772)
Stecker mit Kabel", - "es": "Tiene un conector de
Tipo 1 con cable (J1772)
" + "es": "Tiene un conector de
Tipo 1 con cable (J1772)
", + "ca": "Té un connector de
Tipus 1 amb cable (J1772)
" }, "osmTags": "socket:type1_cable~*" }, @@ -4853,7 +4882,8 @@ "nl": "Heeft een
Tesla Supercharger
", "da": "Har et
Tesla Supercharger
stik", "de": "Verfügt über einen
Tesla Supercharger
Stecker", - "es": "Tiene un conector
Tesla Supercharger
" + "es": "Tiene un conector
Tesla Supercharger
", + "ca": "Té un connector
Tesla Supercharger
" }, "osmTags": "socket:tesla_supercharger~*" }, @@ -4863,7 +4893,8 @@ "nl": "Heeft een
Type 2 (mennekes)
", "da": "Har en
Type 2 (mennekes)
connector", "de": "Hat einen
Typ 2 (Mennekes)
Anschluss", - "es": "Tiene un conector
Tipo 2 (mennekes)
" + "es": "Tiene un conector
Tipo 2 (mennekes)
", + "ca": "Té un connector
Tipus 2 (mennekes)
" }, "osmTags": "socket:type2~*" }, @@ -4873,7 +4904,7 @@ "nl": "Heeft een
Type 2 CCS (mennekes)
", "da": "Har en
Type 2 CCS (mennekes)
connector", "de": "Hat einen
Typ 2 CCS (Mennekes)
Anschluss", - "es": "Tiene un conector
Tipo 2 CCS (mennekes
" + "es": "Tiene un conector
Tipo 2 CCS (mennekes)
" }, "osmTags": "socket:type2_combo~*" }, diff --git a/assets/layers/climbing/climbing.json b/assets/layers/climbing/climbing.json index c335abc827..31e083a22e 100644 --- a/assets/layers/climbing/climbing.json +++ b/assets/layers/climbing/climbing.json @@ -47,7 +47,8 @@ "ja": "ルートの長さは平均で{canonical(climbing:length)}です", "fr": "Les voies font {canonical(climbing:length)} de long en moyenne", "it": "Le vie sono lunghe mediamente {canonical(climbing:length)}", - "es": "Las rutas miden {canonical(climbing:length)} de media" + "es": "Las rutas miden {canonical(climbing:length)} de media", + "ca": "Les rutes mesuren {canonical(climbing:length)} de mitja" }, "question": { "de": "Wie lang sind die Routen (durchschnittlich) in Metern?", @@ -56,7 +57,8 @@ "ja": "ルートの(平均)長さはメートル単位でいくつですか?", "fr": "Quelle est la longueur moyenne des voies en mètres ?", "it": "Quale è la lunghezza (media) delle vie in metri?", - "es": "¿Cual es la longitud (media) de las rutas en metros?" + "es": "¿Cual es la longitud (media) de las rutas en metros?", + "ca": "Quina és la longitud (mitjana) de les rutes en metres?" }, "freeform": { "key": "climbing:length", @@ -129,7 +131,8 @@ "ja": "ここでボルダリングはできますか?", "nb_NO": "Er buldring mulig her?", "fr": "L’escalade de bloc est-elle possible ici ?", - "it": "È possibile praticare ‘bouldering’ qua?" + "it": "È possibile praticare ‘bouldering’ qua?", + "ca": "És possible fer escalda en bloc aquí?" }, "mappings": [ { @@ -141,7 +144,8 @@ "ja": "ボルダリングはここで可能です", "nb_NO": "Buldring er mulig her", "fr": "L’escalade de bloc est possible", - "it": "L’arrampicata su massi è possibile qua" + "it": "L’arrampicata su massi è possibile qua", + "ca": "Aquí és possible l'escalada en bloc" } }, { @@ -153,7 +157,8 @@ "ja": "ここではボルダリングはできません", "nb_NO": "Buldring er ikke mulig her", "fr": "L’escalade de bloc n’est pas possible", - "it": "L’arrampicata su massi non è possibile qua" + "it": "L’arrampicata su massi non è possibile qua", + "ca": "Aquí no és possible l'escalada en bloc" } }, { @@ -164,7 +169,8 @@ "nl": "Bolderen kan hier, maar er zijn niet zoveel routes", "ja": "ボルダリングは可能ですが、少しのルートしかありません", "fr": "L’escalade de bloc est possible sur des voies précises", - "it": "L’arrampicata su massi è possibile anche se su poche vie" + "it": "L’arrampicata su massi è possibile anche se su poche vie", + "ca": "L'escalada en bloc és possible, tot i que només hi ha unes poques rutes" } }, { @@ -175,7 +181,8 @@ "nl": "Er zijn hier {climbing:boulder} bolderroutes", "ja": "{climbing:boulder} ボルダールートがある", "fr": "Il y a {climbing:boulder} voies d’escalade de bloc", - "it": "Sono presenti {climbing:boulder} vie di arrampicata su massi" + "it": "Sono presenti {climbing:boulder} vie di arrampicata su massi", + "ca": "Hi han {climbing:boulder} rutes d'escalada en bloc" }, "hideInAnswer": true } @@ -355,14 +362,16 @@ "es": "¿Se requiere una tasa para escalar aquí?", "de": "Ist das Klettern hier gebührenpflichtig?", "nl": "Moet men betalen om hier te klimmen?", - "fr": "Est-ce que la grimpe sur ce site est payante ?" + "fr": "Est-ce que la grimpe sur ce site est payante ?", + "ca": "Es requereix una tarifa per a pujar aquí?" }, "render": { "en": "A fee of {charge} should be paid for climbing here", "de": "Zum Klettern wird eine Gebühr von {charge} erhoben", "es": "Se debe de pagar una tasa de {charge} para escalar aquí", "nl": "Men moet {charge} betalen om hier te klimmen", - "fr": "Une taxe de {charge} doit être payée pour grimper ici" + "fr": "Une taxe de {charge} doit être payée pour grimper ici", + "ca": "S'ha de pagar una tarifa de {charge} per a escalar aquí" }, "freeform": { "key": "charge", @@ -382,7 +391,8 @@ "de": "Das Klettern ist hier kostenlos", "es": "La escalada es gratis", "nl": "Hier klimmen is gratis", - "fr": "Grimper ici est gratuit" + "fr": "Grimper ici est gratuit", + "ca": "L'escalada en bloc aquí és gratuïta" } }, { @@ -397,7 +407,8 @@ "es": "Hay que pagar una tasa para escalar aquí", "de": "Zum Klettern ist eine Gebühr zu zahlen", "nl": "Men moet betalen om hier te klimmen", - "fr": "Il faut payer une taxe pour grimper ici" + "fr": "Il faut payer une taxe pour grimper ici", + "ca": "Cal pagar una quota per a escalar aquí" }, "hideInAnswer": "charge~*" } diff --git a/assets/layers/clock/clock.json b/assets/layers/clock/clock.json index f46921becf..f4b672364a 100644 --- a/assets/layers/clock/clock.json +++ b/assets/layers/clock/clock.json @@ -32,7 +32,8 @@ "en": "In what way is the clock mounted?", "nl": "Hoe is de klok bevestigd?", "de": "Wie ist die Uhr montiert?", - "ca": "De quina forma està muntat aquest rellotge?" + "ca": "De quina forma està muntat aquest rellotge?", + "fr": "De quelle manière est fixée cette horloge ?" }, "mappings": [ { @@ -41,7 +42,8 @@ "en": "This clock is mounted on a pole", "nl": "Deze klok is bevestigd aan een paal", "de": "Diese Uhr ist auf einem Mast montiert", - "ca": "Aquest rellotge està muntat en un pal" + "ca": "Aquest rellotge està muntat en un pal", + "fr": "Cette horloge est montée sur un poteau" } }, { @@ -50,7 +52,8 @@ "en": "This clock is mounted on a wall", "nl": "Deze klok is bevestigd aan een muur", "de": "Diese Uhr ist an einer Wand montiert", - "ca": "Aquest rellotge està muntat en una paret" + "ca": "Aquest rellotge està muntat en una paret", + "fr": "Cette horloge est fixée sur un mur" } }, { @@ -59,7 +62,8 @@ "en": "This clock is part of a billboard", "nl": "Deze klok is onderdeel van een reclamebord", "de": "Diese Uhr ist Teil einer Werbetafel", - "ca": "Aquest rellotge està muntat en una tanca publicitària" + "ca": "Aquest rellotge està muntat en una tanca publicitària", + "fr": "Cette horloge fait partie d'un panneau publicitaire" } }, { @@ -68,7 +72,8 @@ "en": "This clock is on the ground", "nl": "Deze klok staat op de grond", "de": "Diese Uhr befindet sich auf dem Boden", - "ca": "Aquest rellotge està al sòl" + "ca": "Aquest rellotge està al sòl", + "fr": "Cette horloge est posée au sol" } } ] @@ -79,7 +84,8 @@ "en": "How does this clock display the time?", "nl": "Hoe toont deze klok de tijd?", "de": "Wie zeigt diese Uhr die Zeit an?", - "ca": "Com mostra aquest rellotge l'hora?" + "ca": "Com mostra aquest rellotge l'hora?", + "fr": "Comment cette horloge indique-t-elle l'heure ?" }, "mappings": [ { @@ -88,7 +94,8 @@ "en": "This clock displays the time with hands", "nl": "Deze klok toont de tijd met wijzers", "de": "Diese Uhr zeigt die Zeit mit Zeigern an", - "ca": "Aquest rellotge mostra l'hora amb mans" + "ca": "Aquest rellotge mostra l'hora amb mans", + "fr": "Cette horloge indique l'heure avec des aiguilles" } }, { @@ -97,7 +104,8 @@ "en": "This clock displays the time with digits", "nl": "Deze klok toont de tijd met cijfers", "de": "Diese Uhr zeigt die Zeit mit Ziffern an", - "ca": "Aquest rellotge mostra l'hora amb dígits" + "ca": "Aquest rellotge mostra l'hora amb dígits", + "fr": "Cette horloges indique l'heure avec des chiffres numériques" } }, { @@ -106,7 +114,8 @@ "en": "This clock displays the time with a sundial", "nl": "Deze klok toont de tijd met een zonnewijzer", "de": "Diese Uhr zeigt die Zeit mit einer Sonnenuhr an", - "ca": "Aquest rellotge mostra l'hora amb un rellotge de sol" + "ca": "Aquest rellotge mostra l'hora amb un rellotge de sol", + "fr": "Cette horloge indique l'heure grâce au soleil" } }, { @@ -115,7 +124,8 @@ "en": "This clock displays the time in a non-standard way, e.g using binary, water or something else", "nl": "Deze klok toont de tijd op een niet-standaard manier, bijvoorbeeld met binaire cijfers, water of iets anders", "de": "Diese Uhr zeigt die Zeit auf eine nicht standardisierte Weise an, z. B. mit Binärzeichen, Wasser oder etwas anderem", - "ca": "Aquest rellotge mostra l'hora d'una manera no estàndard, p.e. utilitzant binari, aigua o quelcom més" + "ca": "Aquest rellotge mostra l'hora d'una manera no estàndard, p.e. utilitzant binari, aigua o quelcom més", + "fr": "Cette horloge indique l'heure d'une manière inhabituelle, par ex. en binaire, avec de l'eau, ou autre" } } ] @@ -164,7 +174,8 @@ "en": "Does this clock also display the date?", "nl": "Toont deze klok ook de datum?", "de": "Zeigt diese Uhr auch das Datum an?", - "ca": "Aquest rellotge també mostra la data?" + "ca": "Aquest rellotge també mostra la data?", + "fr": "Cette horloge indique-t-elle également la date ?" }, "mappings": [ { @@ -173,7 +184,8 @@ "en": "This clock also displays the date", "nl": "Deze klok toont ook de datum", "de": "Diese Uhr zeigt auch das Datum an", - "ca": "Aquest rellotge també mostra la data" + "ca": "Aquest rellotge també mostra la data", + "fr": "Cette horloge indique également la date" } }, { @@ -182,7 +194,8 @@ "en": "This clock does not display the date", "nl": "Deze klok toont de datum niet", "de": "Diese Uhr zeigt kein Datum an", - "ca": "Aquest rellotge no mostra la data" + "ca": "Aquest rellotge no mostra la data", + "fr": "Cette horloge n'indique pas la date" } }, { @@ -230,7 +243,8 @@ "en": "This clock does probably not display the temperature", "nl": "Deze klok toont de temperatuur waarschijnlijk niet", "de": "Diese Uhr zeigt wahrscheinlich nicht die Temperatur an", - "ca": "Aquest rellotge probablement no mostra la temperatura" + "ca": "Aquest rellotge probablement no mostra la temperatura", + "fr": "Cette horloge n'indique probablement pas la date" }, "hideInAnswer": true } @@ -281,7 +295,8 @@ "en": "Does this clock also display the humidity?", "nl": "Toont deze klok ook de luchtvochtigheid?", "de": "Zeigt diese Uhr auch die Luftfeuchtigkeit an?", - "ca": "Aquest rellotge també mostra la humitat?" + "ca": "Aquest rellotge també mostra la humitat?", + "fr": "Cette horloge indique-t-elle également l'humidité ?" }, "mappings": [ { @@ -290,7 +305,8 @@ "en": "This clock also displays the humidity", "nl": "Deze klok toont ook de luchtvochtigheid", "de": "Diese Uhr zeigt auch die Luftfeuchtigkeit an", - "ca": "Aquest rellotge també mostra la humitat" + "ca": "Aquest rellotge també mostra la humitat", + "fr": "Cette horloge indique également l'humidité" } }, { @@ -299,7 +315,8 @@ "en": "This clock does not display the humidity", "nl": "Deze klok toont de luchtvochtigheid niet", "de": "Diese Uhr zeigt nicht die Luftfeuchtigkeit an", - "ca": "Aquest rellotge no mostra la humitat" + "ca": "Aquest rellotge no mostra la humitat", + "fr": "Cette horloge n'indique pas l'humidité" } }, { @@ -308,7 +325,8 @@ "en": "This clock does probably not display the humidity", "nl": "Deze klok toont de luchtvochtigheid waarschijnlijk niet", "de": "Diese Uhr zeigt wahrscheinlich nicht die Luftfeuchtigkeit an", - "ca": "Aquest rellotge probablement no mostra la humitat" + "ca": "Aquest rellotge probablement no mostra la humitat", + "fr": "Cette horloge n'indique probablement pas l'humidité" }, "hideInAnswer": true } @@ -320,7 +338,8 @@ "en": "How many faces does this clock have?", "nl": "Hoeveel klokken heeft deze klok?", "de": "Wie viele Zifferblätter hat diese Uhr?", - "ca": "Quantes cares té aquest rellotge?" + "ca": "Quantes cares té aquest rellotge?", + "fr": "Combien de faces a cette horloge ?" }, "freeform": { "key": "faces", @@ -337,7 +356,8 @@ "en": "This clock has {faces} faces", "nl": "Deze klok heeft {faces} klokken", "de": "Diese Uhr hat {faces} Zifferblätter", - "ca": "Aquest rellotge té {faces} cares" + "ca": "Aquest rellotge té {faces} cares", + "fr": "Cette horloge a {faces} faces" }, "mappings": [ { @@ -346,7 +366,8 @@ "en": "This clock has one face", "nl": "Deze klok heeft één klok", "de": "Diese Uhr hat ein Zifferblatt", - "ca": "Aquest rellotge té una cara" + "ca": "Aquest rellotge té una cara", + "fr": "Cette horloge a une face" } }, { @@ -355,7 +376,8 @@ "en": "This clock has two faces", "nl": "Deze klok heeft twee klokken", "de": "Diese Uhr hat zwei Zifferblätter", - "ca": "Aquest rellotge té dues cares" + "ca": "Aquest rellotge té dues cares", + "fr": "Cette horloge a deux faces" } }, { @@ -364,7 +386,8 @@ "en": "This clock has four faces", "nl": "Deze klok heeft vier klokken", "de": "Diese Uhr hat vier Zifferblätter", - "ca": "Aquest rellotge té quatre cares" + "ca": "Aquest rellotge té quatre cares", + "fr": "Cette horloge a quatre faces" } } ] @@ -409,7 +432,8 @@ "en": "A publicly visible clock mounted on a wall", "nl": "Een publiekelijk zichtbare klok aan een muur", "de": "Eine öffentlich sichtbare Uhr an einer Wand", - "ca": "Un rellotge visible públicament muntat en una paret" + "ca": "Un rellotge visible públicament muntat en una paret", + "fr": "Une horloge publique fixée sur un mur" }, "preciseInput": { "preferredBackground": [ diff --git a/assets/layers/cycleways_and_roads/cycleways_and_roads.json b/assets/layers/cycleways_and_roads/cycleways_and_roads.json index 54aa9f340c..8abf78f099 100644 --- a/assets/layers/cycleways_and_roads/cycleways_and_roads.json +++ b/assets/layers/cycleways_and_roads/cycleways_and_roads.json @@ -224,7 +224,8 @@ "nl": "Er is een fietspad aangrenzend aan de weg (gescheiden met verf)", "de": "Es gibt eine Spur neben der Straße (getrennt durch eine Straßenmarkierung)", "es": "Hay un carril a lado de la carretera (separado con pintura)", - "fr": "Il y a une piste cyclable separée de la route" + "fr": "Il y a une piste cyclable separée de la route", + "ca": "Hi ha un carril al costat de la carretera (separat amb pintura)" } }, { diff --git a/assets/layers/defibrillator/defibrillator.json b/assets/layers/defibrillator/defibrillator.json index 02e7deece6..858e5755a6 100644 --- a/assets/layers/defibrillator/defibrillator.json +++ b/assets/layers/defibrillator/defibrillator.json @@ -155,7 +155,7 @@ "if": "access=yes", "then": { "en": "Publicly accessible", - "ca": "Accés lliure", + "ca": "Accessible al públic", "es": "Acceso libre", "fr": "Librement accessible", "nl": "Publiek toegankelijk", @@ -170,7 +170,7 @@ "if": "access=public", "then": { "en": "Publicly accessible", - "ca": "Publicament accessible", + "ca": "Accessible al públic", "es": "Publicament accesible", "fr": "Librement accessible", "nl": "Publiek toegankelijk", @@ -460,7 +460,8 @@ "it": "Qual è il numero identificativo ufficiale di questo dispositivo? (se visibile sul dispositivo)", "de": "Wie lautet die offizielle Identifikationsnummer des Geräts? (falls am Gerät sichtbar)", "sl": "Kakšna je uradna identifikacijska številka te naprave? (če je vidna na napravi)", - "es": "¿Cual es el número de identificación oficial de este dispositivo? (si está visible en el dispositivo)" + "es": "¿Cual es el número de identificación oficial de este dispositivo? (si está visible en el dispositivo)", + "ca": "Quin és el número d'identificació oficial del dispositiu? (si està visible al dispositiu)" }, "freeform": { "type": "text", @@ -510,7 +511,8 @@ "it": "Qual è il numero di telefono per le domande riguardanti questo defibrillatore?", "de": "Wie lautet die Telefonnummer für Fragen zu diesem Defibrillator?", "sl": "Kakšna je telefonska številka za vprašanja o tem defibrilatorju?", - "es": "¿Cual es el número de teléfono para preguntas sobre este desfibrilador?" + "es": "¿Cual es el número de teléfono para preguntas sobre este desfibrilador?", + "ca": "Quin és el número de telèfon on preguntar sobre aquest desfibril·lador?" }, "freeform": { "key": "phone", @@ -622,7 +624,8 @@ "it": "C’è qualcosa di sbagliato riguardante come è stato mappato, che non si è potuto correggere qua? (lascia una nota agli esperti di OpenStreetMap)", "de": "Gibt es einen Fehler in der Kartierung, den Sie hier nicht beheben konnten? (hinterlasse eine Notiz für OpenStreetMap-Experten)", "sl": "Ali je kaj narobe s tem vnosom na zemljevid, in tega niste mogli sami popraviti tu? (pustite opombo OpenStreetMap strokovnjakom)", - "es": "¿Hay algo mal con como esta mapeado, que no pudiste arreglar aquí? (deja una nota para los expertos de OpenStreetMap)" + "es": "¿Hay algo mal con como esta mapeado, que no pudiste arreglar aquí? (deja una nota para los expertos de OpenStreetMap)", + "ca": "Hi ha alguna cosa malament en la manera de com està mapejat això, que no heu pogut solucionar aquí? (deixeu una nota als experts d'OpenStreetMap)" }, "freeform": { "key": "fixme", diff --git a/assets/layers/dogpark/dogpark.json b/assets/layers/dogpark/dogpark.json index 4709c90f03..8f0caa7f61 100644 --- a/assets/layers/dogpark/dogpark.json +++ b/assets/layers/dogpark/dogpark.json @@ -94,7 +94,8 @@ "da": "Denne hundskov er indhegnet", "de": "Dieser Hundepark ist komplett umzäunt", "es": "Este parque para perros está cerrado todo alrededor", - "nl": "Deze hondenweide is volledig omheind" + "nl": "Deze hondenweide is volledig omheind", + "ca": "Aquest parc per a gossos està tancat per tot arreu" } }, { diff --git a/assets/layers/drinking_water/drinking_water.json b/assets/layers/drinking_water/drinking_water.json index d5562981ca..cd6e7c4d46 100644 --- a/assets/layers/drinking_water/drinking_water.json +++ b/assets/layers/drinking_water/drinking_water.json @@ -60,7 +60,8 @@ "it": "una acqua potabile", "ru": "питьевая вода", "id": "air minum", - "hu": "ivóvíz" + "hu": "ivóvíz", + "ca": "una font d'aigua potable" }, "tags": [ "amenity=drinking_water" @@ -77,7 +78,8 @@ "fr": "Ce point d'eau potable est-il toujours opérationnel ?", "de": "Ist diese Trinkwasserstelle noch in Betrieb?", "hu": "Működik-e még ez az ivóvíznyerő hely?", - "es": "¿Todavía esta operativo este punto de agua potable?" + "es": "¿Todavía esta operativo este punto de agua potable?", + "ca": "Aquest punt d'aigua potable continua operatiu?" }, "render": { "en": "The operational status is {operational_status}", @@ -86,7 +88,8 @@ "fr": "L'état opérationnel est {operational_status}", "de": "Der Betriebsstatus ist {operational_status}", "hu": "Működési állapota: {operational_status}", - "es": "El estado operacional es {operational_status}" + "es": "El estado operacional es {operational_status}", + "ca": "L'estat operatiu és {operational_status}" }, "freeform": { "key": "operational_status" @@ -114,7 +117,8 @@ "fr": "Cette fontaine est cassée", "de": "Diese Trinkwasserstelle ist kaputt", "hu": "Ez az ivóvízkút elromlott", - "es": "Esta agua potable está rota" + "es": "Esta agua potable está rota", + "ca": "Aquesta font d'aigua potable està trencada" } }, { @@ -126,7 +130,8 @@ "fr": "Cette fontaine est fermée", "de": "Diese Trinkwasserstelle wurde geschlossen", "hu": "Ez az ivóvízkút el van zárva", - "es": "Esta agua potable está cerrada" + "es": "Esta agua potable está cerrada", + "ca": "Aquesta font d'aigua potable està tancada" } } ], @@ -211,7 +216,8 @@ "en": "This is a decorative fountain of which the water is not drinkable by humans", "nl": "Dit is een decoratieve fontein waarvan het water niet geschikt is om te drinken door mensen", "de": "Dies ist ein Zierbrunnen, dessen Wasser für den Menschen nicht trinkbar ist", - "es": "Esta es una fuente decorativa con agua no potable" + "es": "Esta es una fuente decorativa con agua no potable", + "ca": "Es tracta d'una font decorativa amb aigua no potable" } }, { @@ -226,7 +232,8 @@ "en": "This is a water tap or water pump with non-drinkable water.
Examples are water taps with rain water to tap water for nearby plants
", "nl": "Dit is een waterkraan of waterpomp met ondrinkbaar water.
Bijvoorbeeld een waterkraan met regenwater om planten water mee te gevenBeispiele sind Wasserhähne mit Regenwasser zum Zapfen von Wasser für nahe gelegene Pflanzen
", - "es": "Este es un grifo de agua o una bomba de agua con agua no potable.
Ejemplos son grifos con agua de lluvia o agua del grifo para plantas cercanas
" + "es": "Este es un grifo de agua o una bomba de agua con agua no potable.
Ejemplos son grifos con agua de lluvia o agua del grifo para plantas cercanas
", + "ca": "Es tracta d'una aixeta d'aigua o bomba d'aigua amb aigua no potable.
Per exemple les aixetes d'aigua amb aigua de pluja per aprofitar i regar les plantes properes
" } } ] @@ -264,6 +271,7 @@ "hu": "Ivóvizet adó kutakat megjelenítő réteg", "de": "Eine Ebene mit Trinkwasserbrunnen", "es": "Una capa que muestra fuentes de agua potable", - "fr": "Une couche montrant les fontaines d'eau potable" + "fr": "Une couche montrant les fontaines d'eau potable", + "ca": "Una capa que mostra fonts d'aigua potable" } } \ No newline at end of file diff --git a/assets/layers/fire_station/fire_station.json b/assets/layers/fire_station/fire_station.json index b8eea16b6a..349de0e8cb 100644 --- a/assets/layers/fire_station/fire_station.json +++ b/assets/layers/fire_station/fire_station.json @@ -209,7 +209,7 @@ "nl": "Dit station wordt beheerd door de overheid.", "de": "Die Station wird von einer Behörde betrieben.", "es": "Este parque de bomberos lo opera el gobierno.", - "ca": "Aquest parc l'opera el govern." + "ca": "Aquest parc el gestiona el govern." } }, { @@ -225,7 +225,7 @@ "it": "Questa stazione è gestita dalla comunità oppure un’associazione informale.", "nl": "Dit station wordt beheerd door een informele of gemeenschapsorganisatie.", "de": "Die Feuerwache wird von einer gemeinnützigen Organisation betrieben.", - "ca": "Aquesta estació l'opera una comunitat o organització informal." + "ca": "Aquesta estació la gestiona una comunitat o organització informal." } }, { @@ -241,7 +241,7 @@ "it": "Questa stazione è gestita da un gruppo di volontari ufficiale.", "nl": "Dit station wordt beheerd door een formele groep vrijwilligers.", "de": "Die Feuerwache wird von einer Freiwilligenorganisation betrieben.", - "ca": "Aquest operació l'opera un grup formal de voluntaris." + "ca": "Aquesta estació la gestiona un grup formal de voluntaris." } }, { @@ -257,7 +257,7 @@ "it": "Questa stazione è gestita da privati.", "nl": "Dit station wordt door private organisatie beheerd.", "de": "Die Feuerwache wird von einer privaten Organisation betrieben.", - "ca": "Aquesta estació l'opera una entitat privada." + "ca": "Aquesta estació la gestiona una entitat privada." } } ] diff --git a/assets/layers/fitness_centre/fitness_centre.json b/assets/layers/fitness_centre/fitness_centre.json index f4efa8c522..d029ab229d 100644 --- a/assets/layers/fitness_centre/fitness_centre.json +++ b/assets/layers/fitness_centre/fitness_centre.json @@ -57,7 +57,8 @@ "render": { "en": "This fitness centre is called {name}", "de": "Das Fitnessstudio heißt {name}", - "nl": "Dit fitness-centrum heet {name}" + "nl": "Dit fitness-centrum heet {name}", + "ca": "Aquest gimnàs / centre de fitness s'anomena {name}" } }, "images", diff --git a/assets/layers/food/food.json b/assets/layers/food/food.json index 5d2b26a584..671e8dc7a6 100644 --- a/assets/layers/food/food.json +++ b/assets/layers/food/food.json @@ -484,7 +484,8 @@ "nl": "Eten kan hier afgehaald worden", "de": "Hier werden Gerichte auch zum Mitnehmen angeboten", "es": "Aquí es posible pedir para llevar", - "fr": "La vente à emporter est possible ici" + "fr": "La vente à emporter est possible ici", + "ca": "Aquí és possible demanar per emportar" } }, { @@ -494,7 +495,8 @@ "nl": "Hier is geen afhaalmogelijkheid", "de": "Hier werden Gerichte nicht zum Mitnehmen angeboten", "es": "Aquí no es posible pedir para llevar", - "fr": "La vente à emporter n'est pas possible ici" + "fr": "La vente à emporter n'est pas possible ici", + "ca": "Aquí no és possible demanar per emportar" } } ], @@ -516,7 +518,8 @@ "en": "This business does home delivery (eventually via a third party)", "de": "Dieses Unternehmen liefert nach Hause (eventuell über eine dritte Partei)", "fr": "Ce restaurant effectue la livraison à domicile (éventuellement via un tiers)", - "nl": "Deze zaak levert aan huis (eventueel via een derde partij)" + "nl": "Deze zaak levert aan huis (eventueel via een derde partij)", + "ca": "Aquest negoci fa lliuraments a domicili (eventualment a través d'un tercer)" } }, { @@ -525,7 +528,8 @@ "en": "This business does not deliver at home", "de": "Dieses Unternehmen liefert nicht nach Hause", "fr": "Ce restaurant ne livre pas à domicile", - "nl": "Deze zaak doet geen thuisleveringen" + "nl": "Deze zaak doet geen thuisleveringen", + "ca": "Aquest negoci no fa lliurament a casa" } } ] @@ -536,7 +540,8 @@ "en": "Does this restaurant have a vegetarian option?", "de": "Werden hier vegetarische Gerichte angeboten?", "es": "¿Este restaurante tiene una opción vegetariana?", - "fr": "Ce restaurant propose-t-il une option végétarienne ?" + "fr": "Ce restaurant propose-t-il une option végétarienne ?", + "ca": "Aquest restaurant té opció vegetariana?" }, "mappings": [ { @@ -546,7 +551,8 @@ "nl": "Geen vegetarische opties beschikbaar", "de": "Hier werden keine vegetarischen Gerichte angeboten", "es": "Sin opciones vegetarianas", - "fr": "Aucune option végétarienne n'est disponible" + "fr": "Aucune option végétarienne n'est disponible", + "ca": "No hi ha opcions vegetarianes disponibles" } }, { @@ -556,7 +562,8 @@ "nl": "Beperkte vegetarische opties zijn beschikbaar", "de": "Hier werden nur wenige vegetarische Gerichte angeboten", "es": "Algunas opciones vegetarianas", - "fr": "Certaines options végétariennes sont disponibles" + "fr": "Certaines options végétariennes sont disponibles", + "ca": "Algunes opcions vegetarianes" } }, { @@ -566,7 +573,8 @@ "nl": "Vegetarische opties zijn beschikbaar", "de": "Hier werden vegetarische Gerichte angeboten", "es": "Opciones vegetarianas disponibles", - "fr": "Des options végétariennes sont disponibles" + "fr": "Des options végétariennes sont disponibles", + "ca": "Hi ha opcions vegetarianes disponibles" } }, { @@ -576,7 +584,8 @@ "nl": "Enkel vegetarische opties zijn beschikbaar", "de": "Hier werden ausschließlich vegetarische Gerichte angeboten", "es": "Todos los platos son vegetarianos", - "fr": "Tous les plats sont végétariens" + "fr": "Tous les plats sont végétariens", + "ca": "Tots els plats són vegetarians" } } ], @@ -589,7 +598,8 @@ "nl": "Heeft deze eetgelegenheid een veganistische optie?", "de": "Werden hier vegane Gerichte angeboten?", "es": "¿Este negocio sirve comida vegana?", - "fr": "Cet établissement sert-il des repas végétaliens ?" + "fr": "Cet établissement sert-il des repas végétaliens ?", + "ca": "Aquest negoci serveix menjars vegans?" }, "mappings": [ { @@ -599,7 +609,8 @@ "nl": "Geen veganistische opties beschikbaar", "de": "Hier werden keine veganen Gerichte angeboten", "es": "Sin opciones veganas disponibles", - "fr": "Aucune option végétalienne disponible" + "fr": "Aucune option végétalienne disponible", + "ca": "No hi ha opcions veganes disponibles" } }, { @@ -609,7 +620,8 @@ "nl": "Beperkte veganistische opties zijn beschikbaar", "de": "Hier werden nur wenige vegane Gerichte angeboten", "es": "Alguna opciones veganas disponibles", - "fr": "Certaines options végétaliennes sont disponibles" + "fr": "Certaines options végétaliennes sont disponibles", + "ca": "Hi ha algunes opcions veganes disponibles" } }, { @@ -619,7 +631,8 @@ "nl": "Veganistische opties zijn beschikbaar", "de": "Hier werden vegane Gerichte angeboten", "es": "Opciones veganas disponibles", - "fr": "Des options végétaliennes sont disponibles" + "fr": "Des options végétaliennes sont disponibles", + "ca": "Hi ha opcions veganes disponibles" } }, { @@ -629,7 +642,8 @@ "nl": "Enkel veganistische opties zijn beschikbaar", "de": "Hier werden ausschließlich vegane Gerichte angeboten", "es": "Todos los platos son veganos", - "fr": "Tous les plats sont végétaliens" + "fr": "Tous les plats sont végétaliens", + "ca": "Tots els plats són vegans" } } ], @@ -641,7 +655,8 @@ "en": "Does this restaurant offer a halal menu?", "nl": "Heeft dit restaurant halal opties?", "de": "Werden hier halal Gerichte angeboten?", - "fr": "Ce restaurant propose-t-il un menu halal ?" + "fr": "Ce restaurant propose-t-il un menu halal ?", + "ca": "Aquest restaurant ofereix un menú halal?" }, "mappings": [ { @@ -650,7 +665,8 @@ "en": "There are no halal options available", "nl": "Er zijn geen halal opties aanwezig", "de": "Hier werden keine halal Gerichte angeboten", - "fr": "Il n'y a pas d'options halal disponibles" + "fr": "Il n'y a pas d'options halal disponibles", + "ca": "No hi ha opcions halal disponibles" } }, { @@ -659,7 +675,8 @@ "en": "There is a small halal menu", "nl": "Er zijn een beperkt aantal halal opties", "de": "Hier werden nur wenige halal Gerichte angeboten", - "fr": "Il y a un petit menu halal" + "fr": "Il y a un petit menu halal", + "ca": "Hi ha un petit menú halal" } }, { @@ -668,7 +685,8 @@ "nl": "Halal menu verkrijgbaar", "en": "There is a halal menu", "de": "Hier werden halal Gerichte angeboten", - "fr": "Il y a un menu halal" + "fr": "Il y a un menu halal", + "ca": "Hi ha un menú halal" } }, { @@ -677,7 +695,8 @@ "nl": "Enkel halal opties zijn beschikbaar", "en": "Only halal options are available", "de": "Hier werden ausschließlich halal Gerichte angeboten", - "fr": "Seules les options halal sont disponibles" + "fr": "Seules les options halal sont disponibles", + "ca": "Només hi ha opcions halal disponibles" } } ], @@ -690,7 +709,8 @@ "en": "Does this restaurant offer organic food?", "de": "Bietet dieses Restaurant biologische Speisen an?", "nl": "Biedt dit restaurant biologisch eten?", - "fr": "Ce restaurant propose-t-il de la nourriture bio ?" + "fr": "Ce restaurant propose-t-il de la nourriture bio ?", + "ca": "Aquest restaurant ofereix menjar ecològic?" }, "mappings": [ { @@ -699,7 +719,8 @@ "en": "There are no organic options available", "de": "Es sind keine biologischen Produkte verfügbar", "nl": "Er zijn geen biologische opties beschikbaar", - "fr": "Il n'y a pas d'option bio disponible" + "fr": "Il n'y a pas d'option bio disponible", + "ca": "No hi ha opcions ecològiques disponibles" } }, { @@ -708,7 +729,8 @@ "en": "There is an organic menu", "de": "Es gibt ein biologisches Menü", "nl": "Er is een biologisch menu", - "fr": "Il y a un menu bio" + "fr": "Il y a un menu bio", + "ca": "Hi ha un menú ecològic" } }, { @@ -717,7 +739,8 @@ "en": "Only organic options are available", "de": "Nur biologische Produkte sind erhältlich", "nl": "Er zijn alleen biologische opties beschikbaar", - "fr": "Bio uniquement" + "fr": "Bio uniquement", + "ca": "Només hi ha opcions ecològiques disponibles" } } ], diff --git a/assets/layers/governments/governments.json b/assets/layers/governments/governments.json index 8a501c259a..797a0a993a 100644 --- a/assets/layers/governments/governments.json +++ b/assets/layers/governments/governments.json @@ -41,7 +41,8 @@ "render": { "en": "This Governmental Office is called {name}", "de": "Der Name der Behörde lautet {name}", - "nl": "Deze overheidsdienst heet {name}" + "nl": "Deze overheidsdienst heet {name}", + "ca": "Aquesta Oficina Governamental s'anomena {name}" }, "freeform": { "key": "name" diff --git a/assets/layers/hackerspace/hackerspace.json b/assets/layers/hackerspace/hackerspace.json index 75db7a4473..a1432320a9 100644 --- a/assets/layers/hackerspace/hackerspace.json +++ b/assets/layers/hackerspace/hackerspace.json @@ -353,4 +353,4 @@ ], "allowMove": true, "deletion": true -} +} \ No newline at end of file diff --git a/assets/layers/information_board/information_board.json b/assets/layers/information_board/information_board.json index 38b03bacff..e30903740a 100644 --- a/assets/layers/information_board/information_board.json +++ b/assets/layers/information_board/information_board.json @@ -7,7 +7,7 @@ "fr": "Panneaux d'informations", "de": "Informationstafeln", "ru": "Информационные щиты", - "ca": "Panells d'informació", + "ca": "Taulers informatius", "es": "Paneles informativos" }, "minzoom": 12, @@ -26,7 +26,7 @@ "fr": "Panneau d'informations", "de": "Informationstafel", "ru": "Информационный щит", - "ca": "Panell d'informació", + "ca": "Tauler informatiu", "es": "Panel informativo" } }, @@ -46,7 +46,8 @@ "fr": "une panneau d'informations", "de": "eine Informationstafel", "ru": "информационный щит", - "es": "un panel informativo" + "es": "un panel informativo", + "ca": "un tauler informatiu" } } ], diff --git a/assets/layers/nature_reserve/nature_reserve.json b/assets/layers/nature_reserve/nature_reserve.json index 369d50ef0d..0d766743f9 100644 --- a/assets/layers/nature_reserve/nature_reserve.json +++ b/assets/layers/nature_reserve/nature_reserve.json @@ -60,7 +60,8 @@ "question": { "en": "Is this nature reserve accessible to the public?", "nl": "Is dit gebied toegankelijk?", - "de": "Ist das Gebiet öffentlich zugänglich?" + "de": "Ist das Gebiet öffentlich zugänglich?", + "ca": "Aquesta reserva natural és accessible al públic?" }, "freeform": { "key": "access:description" @@ -76,7 +77,8 @@ "then": { "en": "Publicly accessible", "nl": "Vrij toegankelijk", - "de": "Das Gebiet ist öffentlich zugänglich" + "de": "Das Gebiet ist öffentlich zugänglich", + "ca": "Accessible al públic" } }, { @@ -90,7 +92,8 @@ "en": "Not accessible", "nl": "Niet toegankelijk", "de": "Das Gebiet ist nicht zugänglich", - "es": "No accesible" + "es": "No accesible", + "ca": "No accessible" } }, { @@ -104,7 +107,8 @@ "en": "Not accessible as this is a private area", "nl": "Niet toegankelijk, want privégebied", "de": "Das Gebiet ist privat und nicht zugänglich", - "es": "No accesible, ya que es una área privada" + "es": "No accesible, ya que es una área privada", + "ca": "No accessible perquè es tracta d'una zona privada" } }, { @@ -118,7 +122,8 @@ "en": "Accessible despite being a privately owned area", "nl": "Toegankelijk, ondanks dat het privegebied is", "de": "Das Gebiet ist privat aber zugänglich", - "es": "Accesible a pesar de ser una área privada" + "es": "Accesible a pesar de ser una área privada", + "ca": "Accessible tot i ser una propietat privada" } }, { @@ -132,7 +137,8 @@ "en": "Only accessible with a guide or during organised activities", "nl": "Enkel toegankelijk met een gids of tijdens een activiteit", "de": "Das Gebiet ist nur während Führungen oder organisierten Aktivitäten zugänglich", - "es": "Solo accesible con un guía o durante actividades organizadas" + "es": "Solo accesible con un guía o durante actividades organizadas", + "ca": "Només accessible amb guia o durant les activitats organitzades" } }, { @@ -146,7 +152,8 @@ "en": "Accessible with fee", "nl": "Toegankelijk mits betaling", "de": "Das Gebiet ist nur gegen Bezahlung zugänglich", - "es": "Accesible con una tasa" + "es": "Accesible con una tasa", + "ca": "Accessible amb una taxa" } } ], @@ -163,7 +170,8 @@ "en": "Who operates this area?", "nl": "Wie beheert dit gebied?", "de": "Wer betreibt das Gebiet?", - "es": "¿Quién opera esta área?" + "es": "¿Quién opera esta área?", + "ca": "Qui gestiona aquesta àrea?" }, "freeform": { "key": "operator" @@ -179,7 +187,8 @@ "en": "Operated by Natuurpunt", "nl": "Dit gebied wordt beheerd door Natuurpunt", "de": "Das Gebiet wird betrieben von Natuurpunt", - "es": "Operado por NatuurPunt" + "es": "Operado por NatuurPunt", + "ca": "Gestionat per NatuurPunt" }, "icon": "./assets/layers/nature_reserve/Natuurpunt.jpg" }, @@ -207,7 +216,8 @@ "then": { "en": "Operated by Agentschap Natuur en Bos", "nl": "Dit gebied wordt beheerd door het Agentschap Natuur en Bos", - "de": "Das Gebiet wird betrieben von Agentschap Natuur en Bos" + "de": "Das Gebiet wird betrieben von Agentschap Natuur en Bos", + "ca": "Gestionat per Agentschap Natuur en Bos" }, "icon": "./assets/layers/nature_reserve/ANB.jpg" } @@ -316,7 +326,8 @@ "en": "Whom is the curator of this nature reserve?", "it": "Chi è il curatore di questa riserva naturale?", "fr": "Qui est en charge de la conservation de la réserve ?", - "de": "Wer verwaltet dieses Gebiet?" + "de": "Wer verwaltet dieses Gebiet?", + "ca": "Qui és el conservador d'aquesta reserva natural?" }, "render": { "nl": "{curator} is de beheerder van dit gebied", @@ -345,7 +356,8 @@ "en": "What email adress can one send to with questions and problems with this nature reserve?", "it": "Qual è l’indirizzo email a cui scrivere per fare domande o segnalare problemi su questa riserva naturale?", "fr": "À quelle adresse courriel peut-on envoyer des questions et des problèmes concernant cette réserve naturelle ? ", - "de": "An welche Email-Adresse kann man sich bei Fragen und Problemen zu diesem Gebiet wenden?" + "de": "An welche Email-Adresse kann man sich bei Fragen und Problemen zu diesem Gebiet wenden?", + "ca": "A quina adreça de correu electrònic es pot enviar amb preguntes i problemes amb aquest parc natural?" }, "render": { "nl": "{email}", @@ -377,7 +389,8 @@ "en": "What phone number can one call to with questions and problems with this nature reserve?", "it": "Quale numero di telefono comporre per fare domande o segnalare problemi riguardanti questa riserva naturale?br/>", "fr": "Quel numéro de téléphone peut-on appeler pour poser des questions et résoudre des problèmes concernant cette réserve naturelle ? ", - "de": "Welche Telefonnummer kann man bei Fragen und Problemen zu diesem Gebiet anrufen?" + "de": "Welche Telefonnummer kann man bei Fragen und Problemen zu diesem Gebiet anrufen?", + "ca": "A quin número de telèfon es pot trucar amb preguntes i problemes amb aquest parc natural?" }, "render": { "*": "{phone}" @@ -413,7 +426,8 @@ "en": "Is there some extra info?", "nl": "Is er extra info die je kwijt wil?", "de": "Gibt es zusätzliche Informationen?", - "es": "¿Hay alguna información adicional?" + "es": "¿Hay alguna información adicional?", + "ca": "Hi ha alguna informació addicional?" }, "render": { "en": "Extra info: {description:0}", @@ -450,12 +464,14 @@ "title": { "en": "a nature reserve", "nl": "een natuurreservaat", - "de": "ein Schutzgebiet" + "de": "ein Schutzgebiet", + "ca": "una reserva natural" }, "description": { "en": "Add a missing nature reserve", "nl": "Voeg een ontbrekend, erkend natuurreservaat toe, bv. een gebied dat beheerd wordt door het ANB of natuurpunt", - "de": "Ein fehlendes Naturschutzgebiet hinzufügen" + "de": "Ein fehlendes Naturschutzgebiet hinzufügen", + "ca": "Afegeix una reserva natural que falta" } } ], @@ -487,7 +503,8 @@ "question": { "en": "Dogs are allowed to roam freely", "nl": "Honden mogen vrij rondlopen", - "de": "Hunde dürfen frei herumlaufen" + "de": "Hunde dürfen frei herumlaufen", + "ca": "Els gossos poden anar lliurement" }, "osmTags": "dog=yes" }, @@ -495,7 +512,8 @@ "question": { "en": "Dogs are allowed if they are leashed", "nl": "Honden welkom aan de leiband", - "de": "Hunde nur erlaubt, wenn sie angeleint sind" + "de": "Hunde nur erlaubt, wenn sie angeleint sind", + "ca": "S'admeten gossos si van lligats" }, "osmTags": { "or": [ diff --git a/assets/layers/observation_tower/observation_tower.json b/assets/layers/observation_tower/observation_tower.json index 5e4a293e65..dfec4d9a0c 100644 --- a/assets/layers/observation_tower/observation_tower.json +++ b/assets/layers/observation_tower/observation_tower.json @@ -109,7 +109,7 @@ "nl": "Deze toren is publiek toegankelijk", "de": "Der Turm ist öffentlich zugänglich", "es": "Esta torre es accesible públicamente", - "ca": "Aquesta torre és d'accés públic" + "ca": "Aquesta torre és accessible al públic" } }, { diff --git a/assets/layers/parking/parking.json b/assets/layers/parking/parking.json index 242c823afd..b5346abb73 100644 --- a/assets/layers/parking/parking.json +++ b/assets/layers/parking/parking.json @@ -191,7 +191,8 @@ "en": "There are {capacity:disabled} disabled parking spots", "nl": "Er zijn {capacity:disabled} parkeerplaatsen voor gehandicapten", "de": "Es gibt {capacity:disabled} barrierefreie Stellplätze", - "fr": "Il y a {capacity:disabled} places de stationnement pour personnes à mobilité réduite" + "fr": "Il y a {capacity:disabled} places de stationnement pour personnes à mobilité réduite", + "ca": "Hi ha {capacity:disabled} places d'aparcament per a discapacitats" } }, { diff --git a/assets/layers/picnic_table/picnic_table.json b/assets/layers/picnic_table/picnic_table.json index b9b8731c53..620f145762 100644 --- a/assets/layers/picnic_table/picnic_table.json +++ b/assets/layers/picnic_table/picnic_table.json @@ -111,7 +111,8 @@ "ru": "стол для пикника", "de": "einen Picknick-Tisch", "fr": "une table de pique-nique", - "es": "una mesa de pícnic" + "es": "una mesa de pícnic", + "ca": "una taula de pícnic" } } ], diff --git a/assets/layers/playground/playground.json b/assets/layers/playground/playground.json index 3f66eb5265..87de491bb4 100644 --- a/assets/layers/playground/playground.json +++ b/assets/layers/playground/playground.json @@ -91,7 +91,8 @@ "ru": "Поверхность - трава", "de": "Der Bodenbelag ist aus Gras", "fr": "La surface est en gazon", - "es": "La superficie es hierba" + "es": "La superficie es hierba", + "ca": "La superfície és herba" } }, { @@ -103,7 +104,8 @@ "ru": "Поверхность - песок", "de": "Der Bodenbelag ist aus Sand", "fr": "La surface est en sable", - "es": "La superficie es arena" + "es": "La superficie es arena", + "ca": "La superfície és sorra" } }, { @@ -114,7 +116,8 @@ "it": "La superficie consiste di trucioli di legno", "de": "Der Bodenbelag ist aus Holzschnitzeln", "ru": "Покрытие из щепы", - "fr": "La surface est en copeaux de bois" + "fr": "La surface est en copeaux de bois", + "ca": "La superfície consisteix en estelles" } }, { @@ -126,7 +129,8 @@ "ru": "Поверхность - брусчатка", "de": "Der Bodenbelag ist aus Pflastersteinen", "fr": "La surface est en pavés", - "es": "La superficie es adoquines" + "es": "La superficie es adoquines", + "ca": "La superfície són llambordes" } }, { @@ -138,7 +142,8 @@ "ru": "Поверхность - асфальт", "de": "Der Bodenbelag ist aus Asphalt", "fr": "La surface est en bitume", - "es": "La superficie es asfalto" + "es": "La superficie es asfalto", + "ca": "La superfície és asfalt" } }, { @@ -150,7 +155,8 @@ "ru": "Поверхность - бетон", "de": "Der Bodenbelag ist aus Beton", "fr": "La surface est en béton", - "es": "La superficie es hormigón" + "es": "La superficie es hormigón", + "ca": "La superfície és formigó" } }, { @@ -275,7 +281,8 @@ "en": "What is the maximum age allowed to access this playground?", "it": "Qual è l’età massima per accedere a questo parco giochi?", "fr": "Quel est l’âge maximum autorisé pour utiliser l’aire de jeu ?", - "de": "Bis zu welchem Alter dürfen Kinder auf dem Spielplatz spielen?" + "de": "Bis zu welchem Alter dürfen Kinder auf dem Spielplatz spielen?", + "ca": "Quina és l'edat màxima permesa per accedir al parc infantil?" }, "freeform": { "key": "max_age", @@ -289,7 +296,8 @@ "en": "Who operates this playground?", "it": "Chi è il responsabile di questo parco giochi?", "de": "Wer betreibt den Spielplatz?", - "fr": "Qui est en charge de l’exploitation de l’aire de jeu ?" + "fr": "Qui est en charge de l’exploitation de l’aire de jeu ?", + "ca": "Qui gestiona aquest parc infantil?" }, "render": { "nl": "Beheer door {operator}", @@ -297,7 +305,8 @@ "it": "Gestito da {operator}", "fr": "Exploité par {operator}", "de": "Betrieben von {operator}", - "es": "Operado por {operator}" + "es": "Operado por {operator}", + "ca": "Gestionat per {operator}" }, "freeform": { "key": "operator" @@ -311,7 +320,8 @@ "en": "Is this playground accessible to the general public?", "it": "Questo parco giochi è pubblicamente accessibile?", "de": "Ist der Spielplatz öffentlich zugänglich?", - "fr": "L’aire de jeu est-elle accessible au public ?" + "fr": "L’aire de jeu est-elle accessible au public ?", + "ca": "Aquest parc infantil és accessible al públic en general?" }, "mappings": [ { @@ -334,7 +344,8 @@ "then": { "en": "This is a paid playground", "nl": "Er moet betaald worden om deze speeltuin te mogen gebruiken", - "de": "Der Spielplatz ist gebührenpflichtig" + "de": "Der Spielplatz ist gebührenpflichtig", + "ca": "Aquest és un parc infantil de pagament" }, "addExtraTags": [ "access=customers" @@ -348,7 +359,8 @@ "it": "Accessibile solamente ai clienti dell’attività che lo gestisce", "de": "Der Spielplatz ist nur für Kunden zugänglich", "fr": "Réservée aux clients", - "es": "Solo accesible para clientes del negocio que lo opera" + "es": "Solo accesible para clientes del negocio que lo opera", + "ca": "Només accessible per als clients del negoci que l'opera" }, "addExtraTags": [ "fee=no" @@ -375,7 +387,8 @@ "ru": "Недоступно", "fr": "Non accessible", "de": "Der Spielplatz ist nicht öffentlich zugänglich", - "es": "No accesible" + "es": "No accesible", + "ca": "No accessible" } }, { @@ -383,7 +396,8 @@ "then": { "en": "This is a schoolyard - an outdoor area where the pupils can play during their breaks; but it is not accessible to the general public", "nl": "Dit is een schoolplein - een zone waar de leerlingen kunnen spelen tijdens de pauze. Dit schoolplein is niet toegankelijk voor het publiek", - "de": "Dies ist ein Schulhof - ein Außenbereich, auf dem die Schüler in den Pausen spielen können; er ist jedoch für die Öffentlichkeit nicht zugänglich" + "de": "Dies ist ein Schulhof - ein Außenbereich, auf dem die Schüler in den Pausen spielen können; er ist jedoch für die Öffentlichkeit nicht zugänglich", + "ca": "Es tracta d'un pati de l'escola, una zona exterior on els alumnes poden jugar durant els descansos; però no és accessible al públic en general" } } ] @@ -395,7 +409,8 @@ "en": "What is the email address of the playground maintainer?", "it": "Qual è l’indirizzo email del gestore di questo parco giochi?", "fr": "Quelle est l'adresse électronique du responsable de l'aire de jeux ?", - "de": "Wie lautet die E-Mail Adresse des Spielplatzbetreuers?" + "de": "Wie lautet die E-Mail Adresse des Spielplatzbetreuers?", + "ca": "Quina és l'adreça de correu electrònic del mantenidor del parc infantil?" }, "render": { "nl": "De bevoegde dienst kan bereikt worden via {email}", @@ -420,7 +435,8 @@ "en": "What is the phone number of the playground maintainer?", "fr": "Quel est le numéro de téléphone du responsable du terrain de jeux ?", "it": "Qual è il numero di telefono del gestore del campetto?", - "de": "Wie lautet die Telefonnummer vom Betreiber des Spielplatzes?" + "de": "Wie lautet die Telefonnummer vom Betreiber des Spielplatzes?", + "ca": "Quin és el telèfon del mantenidor del parc infantil?" }, "render": { "nl": "De bevoegde dienst kan getelefoneerd worden via {phone}", @@ -447,7 +463,8 @@ "fr": "Ce terrain de jeux est-il accessible aux personnes en fauteuil roulant ?", "de": "Ist der Spielplatz für Rollstuhlfahrer zugänglich?", "it": "Il campetto è accessibile a persone in sedia a rotelle?", - "ru": "Доступна ли детская площадка пользователям кресел-колясок?" + "ru": "Доступна ли детская площадка пользователям кресел-колясок?", + "ca": "Aquest parc infantil és accessible per a persones en cadira de rodes?" }, "mappings": [ { @@ -459,7 +476,8 @@ "de": "Vollständig zugänglich für Rollstuhlfahrer", "it": "Completamente accessibile in sedia a rotelle", "ru": "Полностью доступна пользователям кресел-колясок", - "es": "Completamente accesible para usuarios de silla de ruedas" + "es": "Completamente accesible para usuarios de silla de ruedas", + "ca": "Totalment accessible per a persones en cadira de rodes" } }, { @@ -471,7 +489,8 @@ "de": "Eingeschränkte Zugänglichkeit für Rollstuhlfahrer", "it": "Accesso limitato in sedia a rotelle", "ru": "Частично доступна пользователям кресел-колясок", - "es": "Acceso limitado para usuarios de silla de ruedas" + "es": "Acceso limitado para usuarios de silla de ruedas", + "ca": "Accessibilitat limitada per a persones en cadira de rodes" } }, { @@ -483,7 +502,8 @@ "de": "Nicht zugänglich für Rollstuhlfahrer", "it": "Non accessibile in sedia a rotelle", "ru": "Недоступна пользователям кресел-колясок", - "es": "No accesible a usuarios de sillas de ruedas" + "es": "No accesible a usuarios de sillas de ruedas", + "ca": "No accessible per a persones en cadira de rodes" } } ] @@ -562,7 +582,8 @@ "then": { "en": "This is a schoolyard - an (outdoor) area where pupils of a school can play during recess and which is not publicly accessible", "nl": "Dit is een schoolplein - een ruimte waar de leerlingen van een school kunnen spelen tijdens de pauze maar die niet publiek toegankelijk is", - "de": "Dies ist ein Schulhof - ein (Außen-)Bereich, auf dem die Schüler einer Schule in den Pausen spielen können und der nicht öffentlich zugänglich ist" + "de": "Dies ist ein Schulhof - ein (Außen-)Bereich, auf dem die Schüler einer Schule in den Pausen spielen können und der nicht öffentlich zugänglich ist", + "ca": "Es tracta d'un pati d'escola: una zona (a l'aire lliure) on els alumnes d'una escola poden jugar durant l'esbarjo i que no és accessible al públic" } } ], diff --git a/assets/layers/postoffices/postoffices.json b/assets/layers/postoffices/postoffices.json index d1cdac874b..8880729759 100644 --- a/assets/layers/postoffices/postoffices.json +++ b/assets/layers/postoffices/postoffices.json @@ -185,7 +185,8 @@ "if": "post_office:brand=DHL Paketshop", "then": { "en": "This location is a DHL Paketshop", - "de": "Dieser Standort ist ein DHL Paketshop" + "de": "Dieser Standort ist ein DHL Paketshop", + "ca": "Aquesta ubicació és una botiga DHL Paketshop" }, "hideInAnswer": "_country!=de" }, @@ -193,7 +194,8 @@ "if": "post_office:brand=Hermes PaketShop", "then": { "en": "This location is a Hermes PaketShop", - "de": "Dieser Standort ist ein Hermes PaketShop" + "de": "Dieser Standort ist ein Hermes PaketShop", + "ca": "Aquesta ubicació és una botiga Hermes PaketShop" }, "hideInAnswer": "_country!=de" }, @@ -202,7 +204,8 @@ "then": { "en": "This location is a PostNL-point", "de": "Dieser Standort ist ein PostNL-Punkt", - "nl": "Deze locatie is een PostNL-punt" + "nl": "Deze locatie is een PostNL-punt", + "ca": "Aquesta ubicació és un punt PostNL" }, "hideInAnswer": { "and": [ @@ -306,7 +309,8 @@ }, "question": { "en": "Can you pick up missed parcels here?", - "de": "Können Sie hier verpasste Pakete abholen?" + "de": "Können Sie hier verpasste Pakete abholen?", + "ca": "Es poden recollir els paquets perduts aquí?" }, "freeform": { "key": "post_office:parcel_pickup", @@ -317,14 +321,16 @@ "if": "post_office:parcel_pickup=yes", "then": { "en": "You can pick up missed parcels here", - "de": "Hier können Sie verpasste Pakete abholen" + "de": "Hier können Sie verpasste Pakete abholen", + "ca": "Podeu recollir els paquets perduts aquí" } }, { "if": "post_office:parcel_pickup=no", "then": { "en": "You can't pick up missed parcels here", - "de": "Sie können hier keine verpassten Pakete abholen" + "de": "Sie können hier keine verpassten Pakete abholen", + "ca": "No podeu recollir paquets perduts aquí" } } ] diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index 6c4a9f53a2..cf839bd012 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -264,7 +264,8 @@ "it": "È ad accesso libero", "ru": "Свободный доступ", "hu": "Nyilvánosan használható", - "es": "Accesible públicamente" + "es": "Accesible públicamente", + "ca": "Accessible al públic" }, "if": "access=yes" }, diff --git a/assets/layers/recycling/recycling.json b/assets/layers/recycling/recycling.json index b1abe26630..1aa0629439 100644 --- a/assets/layers/recycling/recycling.json +++ b/assets/layers/recycling/recycling.json @@ -945,7 +945,8 @@ "then": { "en": "Printer cartridges can be recycled here", "nl": "Inktpatronen kunnen hier gerecycleerd worden", - "de": "Druckerpatronen können hier recycelt werden" + "de": "Druckerpatronen können hier recycelt werden", + "ca": "Els cartutxos d'impressora es poden reciclar aquí" }, "icon": { "path": "./assets/layers/recycling/printer_cartridges.svg", diff --git a/assets/layers/split_road/split_road.json b/assets/layers/split_road/split_road.json index 2b15e115fe..94ed73899f 100644 --- a/assets/layers/split_road/split_road.json +++ b/assets/layers/split_road/split_road.json @@ -18,4 +18,4 @@ "color": "black" } ] -} +} \ No newline at end of file diff --git a/assets/layers/sport_pitch/sport_pitch.json b/assets/layers/sport_pitch/sport_pitch.json index 89b9895b83..a76b63cf56 100644 --- a/assets/layers/sport_pitch/sport_pitch.json +++ b/assets/layers/sport_pitch/sport_pitch.json @@ -178,7 +178,8 @@ "it": "Qual è la superficie di questo campo sportivo?", "ru": "Какое покрытие на этой спортивной площадке?", "de": "Welchen Belag hat der Sportplatz?", - "es": "¿Cual es la superficie de esta pista de deportes?" + "es": "¿Cual es la superficie de esta pista de deportes?", + "ca": "Quina és la superfície d'aquest camp esportiu?" }, "render": { "nl": "De ondergrond is {surface}", @@ -271,7 +272,7 @@ "ru": "Есть ли свободный доступ к этой спортивной площадке?", "de": "Ist der Sportplatz öffentlich zugänglich?", "es": "¿Esta pista de deportes es accesible públicamente?", - "ca": "Aquesta pista d'esports és accessible públicament?" + "ca": "Aquesta pista d'esports és accessible al públic?" }, "mappings": [ { @@ -613,7 +614,7 @@ "en": "Publicly accessible", "nl": "Publiek toegankelijk", "de": "Öffentlich zugänglich", - "ca": "Accés lliure" + "ca": "Accessible al públic" }, "osmTags": { "or": [ diff --git a/assets/layers/street_lamps/street_lamps.json b/assets/layers/street_lamps/street_lamps.json index ac1eb04c0f..35938898ae 100644 --- a/assets/layers/street_lamps/street_lamps.json +++ b/assets/layers/street_lamps/street_lamps.json @@ -197,7 +197,8 @@ "en": "What kind of lighting does this lamp use?", "nl": "Wat voor verlichting gebruikt deze lantaarn?", "de": "Mit welcher Art von Beleuchtung arbeitet diese Straßenlaterne?", - "es": "¿Qué tipo de iluminación utiliza esta lámpara?" + "es": "¿Qué tipo de iluminación utiliza esta lámpara?", + "ca": "Quin tipus d'il·luminació utilitza aquest fanal?" }, "mappings": [ { @@ -383,7 +384,8 @@ "question": { "en": "How many fixtures does this light have?", "nl": "Hoeveel lampen heeft deze lantaarn?", - "de": "Wie viele Leuchten hat diese Straßenlaterne?" + "de": "Wie viele Leuchten hat diese Straßenlaterne?", + "ca": "Quants accessoris té aquest fanal?" }, "condition": "support=pole", "freeform": { diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index e13d6b4894..67041a2480 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -40,7 +40,8 @@ "ru": "общественный туалет ", "it": "una servizi igienici aperti al pubblico", "es": "un baño público", - "da": "et offentligt toilet" + "da": "et offentligt toilet", + "ca": "un lavabo públic" }, "tags": [ "amenity=toilets" @@ -54,7 +55,8 @@ "nl": "een rolstoeltoegankelijke, publiek toilet", "it": "una servizi igienici accessibili per persone in sedia a rotelle", "ru": "tуалет с доступом для пользователей кресел-колясок", - "da": "et toilet med kørestolsvenligt toilet" + "da": "et toilet med kørestolsvenligt toilet", + "ca": "un lavabo amb lavabo accessible per a cadires de rodes" }, "tags": [ "amenity=toilets", @@ -222,7 +224,8 @@ "it": "Quanto costa l'accesso a questi servizi igienici?", "ru": "Сколько стоит посещение туалета?", "es": "¿Cuánto hay que pagar para estos baños?", - "da": "Hvor meget skal man betale for disse toiletter?" + "da": "Hvor meget skal man betale for disse toiletter?", + "ca": "Quant s'ha de pagar per aquests lavabos?" }, "render": { "en": "The fee is {charge}", @@ -232,7 +235,8 @@ "it": "La tariffa è {charge}", "ru": "Стоимость {charge}", "es": "La tasa es {charge}", - "da": "Gebyret er {charge}" + "da": "Gebyret er {charge}", + "ca": "La taxa és {charge}" }, "condition": "fee=yes", "freeform": { @@ -412,7 +416,8 @@ "fr": "Ces toilettes disposent-elles d'une table à langer ?", "nl": "Is er een luiertafel beschikbaar?", "it": "È disponibile un fasciatoio (per cambiare i pannolini)?", - "da": "Findes der puslebord (til bleskift)?" + "da": "Findes der puslebord (til bleskift)?", + "ca": "Hi ha un canviador per a nadons (per a canviar bolquers) disponible?" }, "mappings": [ { @@ -423,7 +428,8 @@ "nl": "Er is een luiertafel", "it": "È disponibile un fasciatoio", "es": "Hay un cambiador", - "da": "Et puslebord er tilgængeligt" + "da": "Et puslebord er tilgængeligt", + "ca": "Hi ha un canviador per a nadons" }, "if": "changing_table=yes" }, diff --git a/assets/layers/toilet_at_amenity/toilet_at_amenity.json b/assets/layers/toilet_at_amenity/toilet_at_amenity.json index 2bbd440d42..41fd31237f 100644 --- a/assets/layers/toilet_at_amenity/toilet_at_amenity.json +++ b/assets/layers/toilet_at_amenity/toilet_at_amenity.json @@ -34,7 +34,8 @@ "en": "Are these toilets publicly accessible?", "de": "Ist die Toilette öffentlich zugänglich?", "nl": "Zijn deze toiletten publiek toegankelijk?", - "fr": "Ces toilettes sont-elles librement accessibles ?" + "fr": "Ces toilettes sont-elles librement accessibles ?", + "ca": "Aquests serveis són d'accés públic?" }, "render": { "en": "Access is {toilets:access}", @@ -71,7 +72,8 @@ "en": "Only access to customers of the amenity", "de": "Nur Zugang für Kunden der Einrichtung", "nl": "Enkel toegankelijk voor klanten van de voorziening", - "fr": "Accessibles uniquement au clients du lieu" + "fr": "Accessibles uniquement au clients du lieu", + "ca": "Només accessible a clients de l'instal·lació" } }, { @@ -121,7 +123,8 @@ "fr": "Ces toilettes sont-elles payantes ?", "nl": "Zijn deze toiletten gratis te gebruiken?", "it": "Questi servizi igienici sono gratuiti?", - "da": "Er det gratis at benytte disse toiletter?" + "da": "Er det gratis at benytte disse toiletter?", + "ca": "Aquest serveis són gratuïts?" }, "mappings": [ { @@ -133,7 +136,8 @@ "ru": "Это платные туалеты", "it": "Questi servizi igienici sono a pagamento", "es": "Estos son baños de pago", - "da": "Det er betalingstoiletter" + "da": "Det er betalingstoiletter", + "ca": "Aquests serveis són de pagament" }, "if": "toilets:fee=yes" }, @@ -145,7 +149,8 @@ "fr": "Toilettes gratuites", "nl": "Gratis te gebruiken", "it": "Gratis", - "da": "Gratis at bruge" + "da": "Gratis at bruge", + "ca": "Gratuït" } } ] @@ -195,7 +200,8 @@ "fr": "Y a-t-il des toilettes réservées aux personnes en fauteuil roulant ?", "nl": "Is er een rolstoeltoegankelijke toilet voorzien?", "it": "C'è un WC riservato alle persone in sedia a rotelle", - "da": "Er der et særligt toilet til kørestolsbrugere?" + "da": "Er der et særligt toilet til kørestolsbrugere?", + "ca": "Hi ha un lavabo específic per a usuaris amb cadira de rodes?" }, "mappings": [ { @@ -206,7 +212,8 @@ "nl": "Er is een toilet voor rolstoelgebruikers", "it": "C'è un WC riservato alle persone in sedia a rotelle", "es": "Hay un baño dedicado para usuarios con sillas de ruedas", - "da": "Der er et særligt toilet til kørestolsbrugere" + "da": "Der er et særligt toilet til kørestolsbrugere", + "ca": "Hi ha un lavabo dedicat per a usuaris amb cadira de rodes" }, "if": "toilets:wheelchair=yes" }, @@ -220,7 +227,8 @@ "it": "Non accessibile in sedia a rotelle", "ru": "Недоступно пользователям кресел-колясок", "es": "Sin acceso para sillas de ruedas", - "da": "Ingen kørestolsadgang" + "da": "Ingen kørestolsadgang", + "ca": "Sense accés per a cadires de rodes" } }, { @@ -229,7 +237,8 @@ "en": "There is only a dedicated toilet for wheelchair users", "nl": "Er is alleen een toilet voor rolstoelgebruikers", "de": "Es gibt nur eine barrierefreie Toilette für Rollstuhlfahrer", - "da": "Der er kun et særligt toilet til kørestolsbrugere" + "da": "Der er kun et særligt toilet til kørestolsbrugere", + "ca": "Sols hi ha un lavabo per a usuaris amb cadira de rodes" } } ] diff --git a/assets/layers/transit_routes/transit_routes.json b/assets/layers/transit_routes/transit_routes.json index 008700bf78..9dc2e15af6 100644 --- a/assets/layers/transit_routes/transit_routes.json +++ b/assets/layers/transit_routes/transit_routes.json @@ -86,7 +86,8 @@ "de": "Die Buslinie startet von {from}", "nl": "Deze buslijn begint bij {from}", "da": "Denne buslinje starter kl. {from}", - "fr": "Cette ligne de bus commence à {from}" + "fr": "Cette ligne de bus commence à {from}", + "ca": "Aquesta línia d'autobús comença a {from}" }, "question": { "en": "What is the starting point for this bus line?", @@ -128,7 +129,8 @@ "de": "Der Endpunkt der Buslinie ist {to}", "nl": "Deze buslijn eindigt bij {to}", "da": "Denne buslinje slutter ved {to}", - "fr": "Cette ligne de bus termine à {to}" + "fr": "Cette ligne de bus termine à {to}", + "ca": "Aquesta línia d'autobús acaba a {to}" }, "question": { "en": "What is the ending point for this bus line?", diff --git a/assets/layers/transit_stops/transit_stops.json b/assets/layers/transit_stops/transit_stops.json index 4310b85ab0..a78d749867 100644 --- a/assets/layers/transit_stops/transit_stops.json +++ b/assets/layers/transit_stops/transit_stops.json @@ -4,7 +4,8 @@ "en": "Transit Stops", "de": "Haltestellen", "da": "Transitstationer", - "fr": "Arrêts de transport en commun" + "fr": "Arrêts de transport en commun", + "ca": "Parades de transport públic" }, "description": { "en": "Layer showing different types of transit stops.", @@ -322,7 +323,8 @@ "en": "This stop has a board showing realtime departure information", "de": "Die Haltestelle hat einen Fahrplan, der Abfahrtszeiten in Echtzeit anzeigt", "da": "Dette stop har en tavle med oplysninger om afgang i realtid", - "fr": "Cet arrêt a un panneau indiquant les départs en temps réel" + "fr": "Cet arrêt a un panneau indiquant les départs en temps réel", + "ca": "Aquesta parada té un tauló amb els horaris en temps real" } }, { @@ -366,7 +368,8 @@ "en": "

{_contained_routes_count} routes stop at this stop

    {_contained_routes}
", "de": "

{_contained_routes_count} Linien halten an der Haltestelle

    {_contained_routes}
", "da": "

{_contained_routes_count} ruter stopper ved dette stoppested

    {_contained_routes}
", - "nl": "

{_contained_routes_count} lijnen stoppen bij deze halte

    {_contained_routes}
" + "nl": "

{_contained_routes_count} lijnen stoppen bij deze halte

    {_contained_routes}
", + "ca": "

{_contained_routes_count} rutes paren a aquesta parada

    {_contained_routes}
" }, "condition": "_contained_routes~*", "id": "contained_routes" @@ -386,7 +389,8 @@ "question": { "en": "With a shelter", "de": "Mit Unterstand", - "fr": "Avec un abri" + "fr": "Avec un abri", + "ca": "Amb refugi" } } ] @@ -422,7 +426,8 @@ "question": { "en": "With a bin", "de": "Mit Mülleimer", - "fr": "Avec un poubelle" + "fr": "Avec un poubelle", + "ca": "Amb paperera" } } ] diff --git a/assets/layers/tree_node/tree_node.json b/assets/layers/tree_node/tree_node.json index 833b2bb64c..934812059d 100644 --- a/assets/layers/tree_node/tree_node.json +++ b/assets/layers/tree_node/tree_node.json @@ -121,7 +121,7 @@ "question": { "en": "What is the circumference of the tree trunk?", "de": "Wie groß ist der Umfang des Baumstammes?", - "fr": "Quelle est la circonférence du tronc ? ", + "fr": "Quelle est la circonférence du tronc ?", "nl": "Wat is de omtrek van de boomstam? ", "es": "¿Cuál es la circunferencia del tronco del árbol?" }, @@ -140,7 +140,7 @@ "questionHint": { "en": "This is measured at a height of 1.30m", "de": "Dies wird in einer Höhe von 1,30 m gemessen", - "fr": "La mesure est effectuée à 1.30m de hauteur", + "fr": "La mesure est effectuée à 1,30 m de hauteur", "nl": "Dit wordt 1.30m boven de grond gemeten", "es": "Se mide a una altura de 1,30 m", "ca": "Es mesura a una alçada d'1,30 m" diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index 0ebc76269f..985db6ddc3 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -531,7 +531,7 @@ "hu": "Kutya bevihető és szabadon szaladgálhat", "it": "I cani sono ammessi e possono andare in giro liberamente", "nb_NO": "Hunder tillates og kan gå fritt", - "ca": "S'accepten gossos lliures", + "ca": "S'accepten gossos i poden estar solts", "sv": "Hundar tillåts och får springa fritt omkring", "zh_Hant": "允許犬隻而且可以自由跑動", "ru": "Собак свободно впускают", @@ -1406,6 +1406,40 @@ "*": "{all_tags()}" } }, + "just_created": { + "description": "This element shows a 'thank you' that the contributor has recently created this element", + "classes": "rounded-xl thanks", + "mappings": [ + { + "if": "id~*", + "icon": "./assets/svg/party.svg", + "then": { + "ca": "Acabeu de crear aquest element! Gràcies per compartir aquesta informació amb el mon i ajudar a persones al voltant del món.", + "de": "Sie haben gerade dieses Element erstellt! Vielen Dank, dass Sie diese Informationen mit der Welt teilen und Menschen weltweit helfen.", + "en": "You just created this element! Thanks for sharing this info with the world and helping people worldwide.", + "fr": "Vous venez de créer cet élément ! Merci d'avoir partagé cette information avec le monde et d'aider les autres personnes.", + "nl": "Je hebt dit punt net toegevoegd! Bedankt om deze info met iedereen te delen en om de mensen wereldwijd te helpen." + } + } + ], + "condition": { + "and": [ + "_backend~*", + "_last_edit:passed_time<300" + ] + }, + "metacondition": { + "and": [ + { + "#": "if _last_edit:contributor:uid is unset, then the point hasn't been uploaded yet", + "or": [ + "_last_edit:contributor:uid:={_uid}", + "_last_edit:contributor:uid=" + ] + } + ] + } + }, "multilevels": { "builtin": "level", "override": { @@ -2049,7 +2083,7 @@ "ca": "Aquest objecte està il·luminat externament, p.e. amb un focus o altres llums", "cs": "Tento objekt je osvětlen zvenčí, např. pomocí reflektoru nebo jiných světel", "de": "Das Objekt wird von außen beleuchtet, z. B. durch Scheinwerfer oder andere Lichter", - "es": "Este objeto recibe iluminación, por ejemplo por un foco u otras luces", + "es": "Este objeto está iluminado desde el exterior, por ejemplo, por un foco u otras luces", "fr": "Cet objet est éclairé par l'extérieur, par ex. par un projecteur ou d'autres lumières", "pl": "Ten obiekt jest oświetlony zewnętrznie, np. przez reflektor lub inne światła" }, @@ -2069,11 +2103,11 @@ "ca": "Aquest objecte no emet llum i no està il·luminat externament", "cs": "Tento objekt nevyzařuje světlo a není osvětlen zvenčí", "de": "Das Objekt wird weder von außen beleuchtet, noch leuchtet es selbst", - "es": "Este objeto ni emite luz ni es iluminado", + "es": "Este objeto no emite luz y no está iluminado por fuentes externas", "fr": "Cet objet n'émet pas de lumière et n'est pas éclairé par l'extérieur", "pl": "Obiekt ten nie emituje światła i nie jest oświetlany z zewnątrz" } } ] } -} \ No newline at end of file +} diff --git a/assets/themes/cyclestreets/cyclestreets.json b/assets/themes/cyclestreets/cyclestreets.json index 04b61a22c0..49814ad517 100644 --- a/assets/themes/cyclestreets/cyclestreets.json +++ b/assets/themes/cyclestreets/cyclestreets.json @@ -723,4 +723,4 @@ } ] } -} +} \ No newline at end of file diff --git a/langs/ca.json b/langs/ca.json index 48cbdf50e1..d2b7381c6d 100644 --- a/langs/ca.json +++ b/langs/ca.json @@ -88,7 +88,6 @@ }, "general": { "about": "Edita facilment i afegeix punts a OpenStreetMap d'una petició determinada", - "aboutMapcomplete": "

Sobre

Utilitza MapComplete per afegir informació a OpenStreetMap sobre un únic tema. Respon preguntes i en minuts les teves contribucions estaran disponibles a tot arreu. En molts temes pots afegir fotografies o fins i tot afegeix \"reviews\". El mantenidor del tema defineix els elements, preguntes i idiomes per a fer-ho possible.

Troba més info

MapComplete sempre ofereix el següent pas per aprendre'n més sobre OpenStreetMap.

  • Quan està incrustat en un lloc web, l'iframe enllaça a un MapComplete a pantalla completa.
  • Aquesta versió ofereix informació sobre OpenStreetMap.
  • La visualització funciona sense iniciar sessió, però l'edició requereix un compte OSM.
  • Si no heu iniciat sessió, se us demanarà que ho feu
  • Un cop hagis respost una sola pregunta, pots afegir noves funcions al mapa
  • Després d'una estona, es mostraran les etiquetes actuals , i després els enllaços a la wiki.


Has trobat alguna incidència? Tens alguna petició ? Vols ajudar a traduir? Vés a per accedir al codi font o al registre d'incidències.

Vols veure els teus progressos ? Segueix el recompte d'edicions a OsmCha.

", "add": { "addNew": "Afegir {category} aquí", "backToSelect": "Selecciona una categoria diferent", @@ -176,7 +175,6 @@ "error": "Algo ha anat mal", "example": "Exemple", "examples": "Exemples", - "feelFreeToSkip": "Podeu afegir més informació a sota, però sentiu-vos lliures de botar preguntes que no conegueu la resposta.", "fewChangesBefore": "Contesta unes quantes preguntes sobre elements existents abans d'afegir-ne un de nou.", "getStartedLogin": "Entra a OpenStreetMap per començar", "getStartedNewAccount": " o crea un nou compte", @@ -214,7 +212,6 @@ "streetcomplete": "Una altra aplicació similar és StreetComplete." }, "nameInlineQuestion": "{category}: El seu nom és $$$", - "newlyCreated": "Acabeu de crear aquest element! Gràcies per compartir aquesta informació amb el mon i ajudar a persones al voltant del món.", "next": "Següent", "noMatchingMapping": "No hi ha cap entrada que coincideixi amb la teva cerca…", "noNameCategory": "{category} sense nom", diff --git a/langs/cs.json b/langs/cs.json index 6806a84f03..48fad68b12 100644 --- a/langs/cs.json +++ b/langs/cs.json @@ -88,10 +88,8 @@ }, "general": { "about": "Snadné úpravy a přidávání OpenStreetMap pro určité téma", - "aboutMapcomplete": "

O službě

Pomocí MapComplete můžete přidávat informace z OpenStreetMap na samostatné téma. Odpovězte na otázky a během několika minut jsou vaše příspěvky dostupné všude. Ve většině témat můžete přidávat obrázky nebo dokonce zanechat hodnocení. Správce tématu pro něj definuje prvky, otázky a jazyky.

Další informace

MapComplete vždy nabízí další krok k získání dalších informací o OpenStreetMap.

  • Při vložení do webové stránky odkazuje iframe na MapComplete na celou obrazovku.
  • Verze na celou obrazovku nabízí informace o OpenStreetMap.
  • Prohlížení funguje bez přihlášení, ale editace vyžaduje účet OSM.
  • Pokud nejste přihlášeni, jste k tomu vyzváni
  • Po zodpovězení jedné otázky můžete do mapy přidávat nové funkce
  • Po chvíli se zobrazí aktuální značky OSM, později odkaz na wiki


Všimli jste si problému? Máte požadavek na funkci? Chcete pomoci s překladem? Přejděte na zdrojový kód nebo sledovač problémů.

Chcete se podívat na svůj pokrok? Sledujte počet úprav na OsmCha.

", "add": { "addNew": "Přidat {category}", - "addNewMapLabel": "Klikněte zde pro přidání nové položky", "backToSelect": "Vyberte jinou kategorii", "confirmButton": "Přidat kategorii {category}
Váš příspěvek je viditelný pro všechny
", "confirmIntro": "

Přidat {title}?

Funkce, kterou zde vytvoříte, bude viditelná pro všechny. Prosíme, přidávejte věci na mapu pouze tehdy, pokud skutečně existují. Tato data využívá mnoho aplikací.", diff --git a/langs/da.json b/langs/da.json index 70aea315d8..9e8a0a3c65 100644 --- a/langs/da.json +++ b/langs/da.json @@ -41,7 +41,6 @@ }, "general": { "about": "Ret og tilføj nemt til Openstreetmap for et bestemt tema", - "aboutMapcomplete": "

Om MapComplete

Brug det til at tilføje OpenStreetMap information om et bestemt tema. Besvar spørgsmål, og i løbet af få minutter er dine bidrag tilgængelige overalt. temabestyreren definerer elementer, spørgsmål og sprog for temaet.

Find ud af mere

MapComplete tilbyder altid det næste trin for at lære mere om OpenStreetMap.

  • Når det er indlejret på et websted, linker iframe-en til et fuldskærms MapComplete
  • Fuldskærmsversionen tilbyder information om OpenStreetMap
  • Man kan se det uden at logge ind, men redigering kræver en OSM-konto.
  • Hvis du ikke har logget ind, vil du blive bedt om at gøre det
  • Når du har besvaret et enkelt spørgsmål, kan du tilføje nye punkter til kortet
  • Efter et stykke tid bliver faktiske OSM-tags vist, senere igen links til wiki-en


Bemærkede du et problem? Har du enanmodning om en ny funktion? Vil du hjælpe med at oversætte? Gå til kildekoden eller issue trackeren.

Vil du se dit fremskridt? Følg antallet af rettelser på OsmCha.

", "add": { "addNew": "Tilføj {category}", "confirmButton": "Tilføj en {category}
Din tilføjelse er synlig for alle
", diff --git a/langs/de.json b/langs/de.json index f19187eee4..0931ed5aff 100644 --- a/langs/de.json +++ b/langs/de.json @@ -88,7 +88,6 @@ }, "general": { "about": "OpenStreetMap für ein bestimmtes Thema einfach bearbeiten und hinzufügen", - "aboutMapcomplete": "

Über

Verwenden Sie MapComplete, um themenbezogene Informationen zu OpenStreetMap hinzuzufügen. Beantworten Sie Fragen, und in wenigen Minuten sind Ihre Beiträge überall verfügbar. Zu den meisten Themen können Sie Bilder hinzufügen oder eine Bewertung hinterlassen. Der Theme-Maintainer definiert dafür Elemente, Fragen und Sprachen.

Mehr erfahren

MapComplete bietet immer den nächsten Schritt, um mehr über OpenStreetMap zu erfahren.

  • In einer Website eingebettet, verlinkt der iframe zu einer Vollbildversion von MapComplete
  • Die Vollbildversion bietet Informationen über OpenStreetMap
  • Das Betrachten funktioniert ohne Anmeldung, das Bearbeiten erfordert ein OSM-Konto.
  • Wenn Sie nicht angemeldet sind, werden Sie dazu aufgefordert
  • Sobald Sie eine Frage beantwortet haben, können Sie der Karte neue Objekte hinzufügen
  • Nach einer Weile werden aktuelle OSM-Tags angezeigt, die später mit dem Wiki verlinkt werden


Haben Sie ein Problem bemerkt? Haben Sie einen Funktionswunsch? Möchten Sie bei der Übersetzung helfen? Hier geht es zum Quellcode und Issue Tracker

Möchten Sie Ihre Bearbeitungen sehen? Verfolgen Sie Ihre Änderungen auf OsmCha.

", "add": { "addNew": "{category} hinzufügen", "backToSelect": "Wählen Sie eine andere Kategorie", @@ -176,7 +175,6 @@ "error": "Etwas ist schief gelaufen", "example": "Beispiel", "examples": "Beispiele", - "feelFreeToSkip": "Sie können unten weitere Informationen hinzufügen oder aktualisieren, aber natürlich können Sie auch Fragen überspringen, auf die Sie keine Antwort wissen.", "fewChangesBefore": "Bitte beantworten Sie einige Fragen zu bestehenden Objekten, bevor Sie ein neues Objekt hinzufügen.", "getStartedLogin": "Bei OpenStreetMap anmelden, um loszulegen", "getStartedNewAccount": " oder ein neues Konto anlegen", @@ -214,7 +212,6 @@ "streetcomplete": "Eine andere, ähnliche App ist StreetComplete." }, "nameInlineQuestion": "Der Name dieser {category} ist $$$", - "newlyCreated": "Sie haben gerade dieses Element erstellt! Vielen Dank, dass Sie diese Informationen mit der Welt teilen und Menschen weltweit helfen.", "next": "Weiter", "noMatchingMapping": "Keine Einträge passen zu Ihrer Suche…", "noNameCategory": "{category} ohne Namen", diff --git a/langs/en.json b/langs/en.json index e7212d6518..b744bdf892 100644 --- a/langs/en.json +++ b/langs/en.json @@ -88,8 +88,9 @@ }, "general": { "about": "Easily edit and add OpenStreetMap for a certain theme", - "aboutMapcomplete": "

Use MapComplete to add OpenStreetMap info on a single theme. Answer questions, and within minutes your contributions are available everywhere. In most themes you can add pictures or even leave a review. The theme maintainer defines elements, questions and languages for it.

Find out more

MapComplete always offers the next step to learn more about OpenStreetMap.

  • When embedded in a website, the iframe links to a full-screen MapComplete.
  • The fullscreen version offers info about OpenStreetMap.
  • Viewing works without login, but editing requires an OSM account.
  • If you are not logged in, you are asked to do so
  • Once you answered a single question, you can add new features to the map
  • After a while, actual OSM-tags are shown, later linking to the wiki


Did you notice an issue? Do you have a feature request? Want to help translate? Head over to the source code or issue tracker.

Want to see your progress? Follow the edit count on OsmCha.

", - "aboutMapcompleteTitle": "About MapComplete", + "aboutMapComplete": { + "intro": "Use MapComplete to add OpenStreetMap info on a single theme. Answer questions, and within minutes your contributions are available everywhere. In most themes you can add pictures or even leave a review. The theme maintainer defines elements, questions and languages for it." + }, "add": { "addNew": "Add {category}", "backToSelect": "Select a different category", @@ -181,7 +182,6 @@ "error": "Something went wrong", "example": "Example", "examples": "Examples", - "feelFreeToSkip": "You can add or update more information below, but feel free to skip questions you don't know the answer to.", "fewChangesBefore": "Please, answer a few questions of existing features before adding a new feature.", "getStartedLogin": "Log in with OpenStreetMap to get started", "getStartedNewAccount": " or create a new account", @@ -223,7 +223,6 @@ "streetcomplete": "Another, similar application is StreetComplete." }, "nameInlineQuestion": "The name of this {category} is $$$", - "newlyCreated": "You just created this element! Thanks for sharing this info with the world and helping people worldwide.", "next": "Next", "noMatchingMapping": "No entries match your search…", "noNameCategory": "{category} without a name", diff --git a/langs/es.json b/langs/es.json index 98a7e8cd3e..f0f8fac52f 100644 --- a/langs/es.json +++ b/langs/es.json @@ -41,7 +41,6 @@ }, "general": { "about": "Edita fácilmente y añade puntos en OpenStreetMap de un tema concreto", - "aboutMapcomplete": "

Aceca de MapComplete

Lo utilizamos para añadir información de OpenStreetMap en un único tema. Responde preguntas, y en minutos tus contribuciones estarán disponibles en todos lados. El mantenedor del tema define elementos, preguntas e idiomas para él.

Descubre más

MapComplete siempre ofrece el siguiente paso para aprender más sobre OpenStreetMap.

  • Cuando se embebe en un sitio web, el iframe enlaza a un MapComplete a pantalla completa
  • La versión a pantalla completa ofrece información sobre OpenStreetMpa
  • Se puede ver el trabajo sin iniciar sesión, pero la edición requiere una cuenta de OSM.
  • Si no has iniciado sesión, se te pedirá que lo hagas
  • Una vez que hayas respondido a una simple pregunta, podrás añadir nuevos puntos al mapa
  • Después de un poco, las etiquetas de OSM se mostrarán, después de enlazar a la wiki


¿Te fijaste en un problema? Tienes una petición de característica?¿Quieres ayudar a traducir? Ve al código fuente o issue tracker.

¿Quieres ver tu progreso? Sigue a la cuenta de ediciones en OsmCha.

", "add": { "addNew": "Añadir {category}", "confirmButton": "Añadir una {category} .
Tu contribución es visible para todos
", diff --git a/langs/fr.json b/langs/fr.json index 0f3a758440..15b2c80d81 100644 --- a/langs/fr.json +++ b/langs/fr.json @@ -59,7 +59,6 @@ }, "general": { "about": "Éditer facilement et ajouter OpenStreetMap pour un certain thème", - "aboutMapcomplete": "

À propos de MapComplete

Avec MapComplete vous pouvez enrichir OpenStreetMap d'informations sur un thème unique. Répondez à quelques questions, et en quelques minutes vos contributions seront disponibles dans le monde entier ! Le concepteur du thème définis les éléments, questions et langues pour le thème.

En savoir plus

MapComplete propose toujours l'étape suivante pour en apprendre plus sur OpenStreetMap.

  • Lorsqu'il est intégré dans un site Web, l'iframe pointe vers MapComplete en plein écran
  • La version plein écran donne des informations sur OpenStreetMap
  • Il est possible de regarder sans se connecter, mais l'édition demande une connexion à OSM.
  • Si vous n'êtes pas connecté, il vous est demandé de le faire
  • Une fois que vous avez répondu à une seule question, vous pouvez ajouter de nouveaux points à la carte
  • Au bout d'un moment, les vrais tags OSM sont montrés, qui pointent ensuite vers le wiki


Vous avez remarqué un problème ? Vous souhaitez demander une fonctionnalité ? Vous voulez aider à traduire ? Allez voir le code source ou le gestionnaire de tickets.

Vous voulez visualiser votre progression ? Suivez le compteur d'édition sur OsmCha.

", "add": { "addNew": "Ajouter {category}", "backToSelect": "Sélectionner une catégorie différente", @@ -147,7 +146,6 @@ "error": "Quelque chose ne s'est pas passé correctement", "example": "Exemple", "examples": "Exemples", - "feelFreeToSkip": "Vous pouvez ajouter ou mettre à jour d'autres informations ci-dessous, mais n'hésitez pas à passer les questions auxquels vous ne savez pas répondre.", "fewChangesBefore": "Merci de répondre à quelques questions à propos de points déjà existants avant d'ajouter de nouveaux points.", "getStartedLogin": "Connectez-vous avec OpenStreetMap pour commencer", "getStartedNewAccount": " ou créez un compte", @@ -178,7 +176,6 @@ "streetcomplete": "Une autre application similaire est StreetComplete." }, "nameInlineQuestion": "Le nom de cet/cette {category} est $$$", - "newlyCreated": "Vous venez de créer cet élément ! Merci d'avoir partagé cette information avec le monde et d'aider les autres personnes.", "next": "Suivant", "noMatchingMapping": "Aucun résultat ne correspond à votre recherche…", "noNameCategory": "{category} sans nom", diff --git a/langs/hu.json b/langs/hu.json index 3dad305cea..9a2d5a96f9 100644 --- a/langs/hu.json +++ b/langs/hu.json @@ -39,7 +39,6 @@ }, "general": { "about": "Egy adott téma esetében az OpenStreetMap egyszerű szerkesztése és hozzáadása", - "aboutMapcomplete": "

Névjegy

A MapComplete-et használhatod, hogy egy egy adott téma szerint OpenStreetMap-adatokat adj hozzá az adatbázishoz. Válaszolj a kérdésekre, és a szerkesztéseid perceken belül mindenhol elérhetővé válnak. A legtöbb témánál hozzáadhatsz képeket vagy akár véleményt is írhatsz. A témához tartozó elemeket, kérdéseket és nyelveket a téma karbantartója határozza meg .

További információk

A MapComplete mindig felkínálja a következő lépést ahhoz, hogy tanulhass az OpenStreetMapről.

  • Weboldalba ágyazva az iframe egy teljes képernyős MapComplete-hez vezet
  • A teljes képernyős változat az OpenStreetMapről mutat adatokat
  • A megtekintés bejelentkezés nélkül is működik, de a szerkesztéshez OSM-fiók szükséges
  • Ha nem vagy bejelentkezve, kérjük, tedd meg
  • Miután válaszoltál egy kérdésre, új elemeket helyezhetsz a térképre
  • Egy idő után megjelennek a tényleges OSM-címkék, amelyek később a wikire hivatkoznak


Észrevettél egy problémát? Új funkciót szeretnél kérni? Szeretnél segíteni a fordításban? Látogass el a forráskódhoz vagy a problémakövetőhöz (issue tracker).

Szeretnéd látni a fejlődést? Kövesd a szerkesztések számát az OsmCha módosításkészlet-elemzőn.

", "add": { "addNew": "Új {category} hozzáadása", "confirmButton": "{category} hozzáadása.
A hozzáadott objektum mindenki számára látható lesz
", diff --git a/langs/it.json b/langs/it.json index 5c00eb76a9..e2574e53f7 100644 --- a/langs/it.json +++ b/langs/it.json @@ -41,7 +41,6 @@ }, "general": { "about": "Modifica e aggiungi con semplicità OpenStreetMap per un certo tema", - "aboutMapcomplete": "

Informazioni

Con MapComplete puoi arricchire OpenStreetMap con informazioni su un singolo argomento. Rispondi a poche domande e in pochi minuti i tuoi contributi saranno disponibili a tutto il mondo! L’utente gestore del tema definisce gli elementi, le domande e le lingue per quel tema.

Scopri altro

MapComplete propone sempre un passo in più per imparare qualcosa di nuovo su OpenStreetMap.

  • Quando viene incorporato in un sito web, il collegamento dell’iframe punta a MapComplete a tutto schermo
  • La versione a tutto schermo fornisce informazioni su OpenStreetMap
  • La visualizzazione non necessita di alcun accesso ma per modificare occorre aver effettuato l’accesso su OSM.
  • Se non hai effettuato l’accesso, ti verrà richiesto di farlo
  • Dopo aver risposto ad una sola domanda potrai aggiungere dei nuovi punti alla mappa
  • Dopo qualche momento verranno mostrate le etichette effettive, in seguito i collegamenti alla wiki


Hai trovato un errore? Vuoi richiedere nuove funzionalità? Vuoi aiutare con la traduzione? Dai un’occhiata al codice sorgente oppure al tracker degli errori.

Vuoi vedere i tuoi progressi?Segui il contatore delle modifiche su OsmCha.

", "add": { "addNew": "Aggiungi {category} qua", "confirmButton": "Aggiungi una {category} qua.
La tua aggiunta è visibile a chiunque
", diff --git a/langs/ja.json b/langs/ja.json index ca98afd70f..d0107892cc 100644 --- a/langs/ja.json +++ b/langs/ja.json @@ -16,7 +16,6 @@ }, "general": { "about": "特定のテーマに沿って、OpenStreetMapを簡単に編集し、情報を追加できます", - "aboutMapcomplete": "

MapCompleteについて

MapCompleteを使えば、1つのテーマに関する情報でOpenStreetMapを充実させることができます。いくつかの質問に答えると、数分以内にあなたの投稿が世界中で公開されます!テーマメンテナは、テーマの要素、質問、言語を定義します。

詳細情報を見る

MapCompleteは常にOpenStreetMapについてさらに学ぶため次のステップを提供します。

  • Webサイトに埋め込まれるとiframeはフルスクリーンのMapCompleteにリンクします
  • フルスクリーン版はOpenStreetMapに関する情報を提供します
  • ログインせずに表示することはできますが、編集にはOSMログインが必要です。
  • ログインしていない場合は、ログインするように求められます
  • 1つの質問に回答すると、マップに新しいポイントを追加できます
  • しばらくすると、実際のOSMタグが表示され、後でWikiにリンクされます


問題に気づきましたか?機能要求はありますか?翻訳の手伝いをしますか?ソースコードまたは問題追跡ツールに移動します。

進捗状況を確認しますか? OsmChaの編集数に従います。

", "add": { "addNew": "ここに新しい {category} を追加します", "confirmButton": "ここに{category}を追加します。
追加内容はすべてのユーザーに表示されます。
", diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 9cc8273059..d2fae71204 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -35,16 +35,6 @@ "1": { "title": "un mupi" }, - "10": { - "description": "S'utilitza per a cartells publicitaris, retols de neó, logotips i cartells en entrades institucionals", - "title": "un lletrer" - }, - "11": { - "title": "una escupltura" - }, - "12": { - "title": "una paret pintada" - }, "2": { "title": "un mupi sobre la paret" }, @@ -71,6 +61,16 @@ }, "9": { "title": "un tòtem" + }, + "10": { + "description": "S'utilitza per a cartells publicitaris, retols de neó, logotips i cartells en entrades institucionals", + "title": "un lletrer" + }, + "11": { + "title": "una escupltura" + }, + "12": { + "title": "una paret pintada" } }, "tagRenderings": { @@ -165,9 +165,6 @@ "1": { "then": "Açò és un tauló d'anunis" }, - "10": { - "then": "Açò és una paret pintada" - }, "2": { "then": "Açò és una columna" }, @@ -191,6 +188,9 @@ }, "9": { "then": "Açò és un tòtem" + }, + "10": { + "then": "Açò és una paret pintada" } }, "question": "Quin tipus d'element publicitari és aquest?", @@ -205,9 +205,6 @@ "1": { "then": "Tauló d'anuncis" }, - "10": { - "then": "Paret Pintada" - }, "2": { "then": "Mupi" }, @@ -231,6 +228,9 @@ }, "9": { "then": "Tòtem" + }, + "10": { + "then": "Paret Pintada" } } } @@ -309,15 +309,6 @@ "1": { "then": "Mural" }, - "10": { - "then": "Azulejo (Rajoles decoratives espanyoles i portugueses)" - }, - "11": { - "then": "Enrajolat" - }, - "12": { - "then": "Tallat a la fusta" - }, "2": { "then": "Pintura" }, @@ -341,6 +332,15 @@ }, "9": { "then": "Relleu" + }, + "10": { + "then": "Azulejo (Rajoles decoratives espanyoles i portugueses)" + }, + "11": { + "then": "Enrajolat" + }, + "12": { + "then": "Tallat a la fusta" } }, "question": "Quin tipus d'obra és aquesta peça?", @@ -1049,11 +1049,6 @@ } }, "question": "Hi ha eines aquí per reparar la teva pròpia bicicleta?" - }, - "opening_hours": { - "override": { - "question": "Quan obri aquest cafè ciclista?" - } } }, "title": { @@ -1352,11 +1347,6 @@ "question": "Quines vàlvules són compatibles?", "render": "Aquesta bomba admet les vàlvules següents: {valves}" }, - "opening_hours_24_7": { - "override": { - "question": "Quan està obert aquest punt de reparació de bicicletes?" - } - }, "send_email_about_broken_pump": { "render": { "special": { @@ -1741,9 +1731,6 @@ "1": { "question": "Té un connector
Schuko sense pin de terra (CEE7/4 tipus F)
connector" }, - "13": { - "question": "Té un connector
Tesla Supercharger (Destination) (Tipus 2 amb un cable de marca tesla)
" - }, "4": { "question": "Té un connector de
Tipus 1 amb cable (J1772)
" }, @@ -1758,6 +1745,9 @@ }, "8": { "question": "Té un connector
Tipus 2 (mennekes)
" + }, + "13": { + "question": "Té un connector
Tesla Supercharger (Destination) (Tipus 2 amb un cable de marca tesla)
" } } } @@ -1807,6 +1797,30 @@ "1": { "then": "Endoll de paret Schuko sense pin a terra (CEE7/4 tipus F)" }, + "2": { + "then": "Endoll de paret Europeu amb pin de terra (CEE7/4 tipus E)" + }, + "3": { + "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" + }, + "4": { + "then": "CHAdeMo" + }, + "5": { + "then": "Chademo" + }, + "6": { + "then": "Tipus 1 amb cable (J1772)" + }, + "7": { + "then": "Tipus 1 amb cable (J1772)" + }, + "8": { + "then": "Tipus 1 sense cable (J1772)" + }, + "9": { + "then": "Tipus 1 sense cable (J1772)" + }, "10": { "then": "CSS 1Tipus 1 (també conegut com Tipus 1 combo)" }, @@ -1837,9 +1851,6 @@ "19": { "then": "Tipus 2 amb cable (mennekes)" }, - "2": { - "then": "Endoll de paret Europeu amb pin de terra (CEE7/4 tipus E)" - }, "20": { "then": "CSS Supercarregador Tesla (tipus2_css de la marca)" }, @@ -1857,27 +1868,6 @@ }, "26": { "then": "USB per a carregar mòbils i dispositius petits" - }, - "3": { - "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" - }, - "4": { - "then": "CHAdeMo" - }, - "5": { - "then": "Chademo" - }, - "6": { - "then": "Tipus 1 amb cable (J1772)" - }, - "7": { - "then": "Tipus 1 amb cable (J1772)" - }, - "8": { - "then": "Tipus 1 sense cable (J1772)" - }, - "9": { - "then": "Tipus 1 sense cable (J1772)" } }, "question": "Quins tipus de connexions de càrrega estan disponibles aquí?" @@ -2865,21 +2855,6 @@ "1": { "then": "Això és una fregiduria" }, - "10": { - "then": "Aquí es serveixen plats xinesos" - }, - "11": { - "then": "Aquí es serveixen plats grecs" - }, - "12": { - "then": "Aquí es serveixen plats indis" - }, - "13": { - "then": "Aquí es serveixen plats turcs" - }, - "14": { - "then": "Aquí es serveixen plats tailandesos" - }, "2": { "then": "Principalment serveix pasta" }, @@ -2900,6 +2875,21 @@ }, "9": { "then": "Aquí es serveixen plats francesos" + }, + "10": { + "then": "Aquí es serveixen plats xinesos" + }, + "11": { + "then": "Aquí es serveixen plats grecs" + }, + "12": { + "then": "Aquí es serveixen plats indis" + }, + "13": { + "then": "Aquí es serveixen plats turcs" + }, + "14": { + "then": "Aquí es serveixen plats tailandesos" } }, "question": "Quin menjar es serveix aquí?", @@ -3956,6 +3946,30 @@ "1": { "question": "Reciclatge de piles" }, + "2": { + "question": "Reciclatge de cartrons de begudes" + }, + "3": { + "question": "Reciclatge de llaunes" + }, + "4": { + "question": "Reciclatge de roba" + }, + "5": { + "question": "Reciclatge d'oli de cuina" + }, + "6": { + "question": "Reciclatge d'oli de motor" + }, + "7": { + "question": "Reciclatge de tubs fluorescents" + }, + "8": { + "question": "Reciclatge de residus verds" + }, + "9": { + "question": "Reciclatge d'ampolles de vidre" + }, "10": { "question": "Reciclatge de vidre" }, @@ -3986,32 +4000,8 @@ "19": { "question": "Reciclatge del rebuig" }, - "2": { - "question": "Reciclatge de cartrons de begudes" - }, "20": { "question": "Reciclatge del rebuig" - }, - "3": { - "question": "Reciclatge de llaunes" - }, - "4": { - "question": "Reciclatge de roba" - }, - "5": { - "question": "Reciclatge d'oli de cuina" - }, - "6": { - "question": "Reciclatge d'oli de motor" - }, - "7": { - "question": "Reciclatge de tubs fluorescents" - }, - "8": { - "question": "Reciclatge de residus verds" - }, - "9": { - "question": "Reciclatge d'ampolles de vidre" } } }, @@ -4074,6 +4064,30 @@ "1": { "then": "Aquí es poden reciclar els cartons de begudes" }, + "2": { + "then": "Aquí es poden reciclar llaunes" + }, + "3": { + "then": "Aquí es pot reciclar roba" + }, + "4": { + "then": "Aquí es pot reciclar oli de cuina" + }, + "5": { + "then": "Aquí es pot reciclar oli de motor" + }, + "6": { + "then": "Aquí es poden reciclar tub fluroescents" + }, + "7": { + "then": "Aquí es poden reciclar residus verds" + }, + "8": { + "then": "Ací es poden reciclar residus orgànics" + }, + "9": { + "then": "Aquí es poden reciclar ampolles de vidre" + }, "10": { "then": "Aquí es pot reciclar vidre" }, @@ -4104,9 +4118,6 @@ "19": { "then": "Aquí es poden reciclar sabates" }, - "2": { - "then": "Aquí es poden reciclar llaunes" - }, "20": { "then": "Aquí es poden reciclar petits electrodomèstics" }, @@ -4118,27 +4129,6 @@ }, "23": { "then": "Ací es pot reciclar el rebuig" - }, - "3": { - "then": "Aquí es pot reciclar roba" - }, - "4": { - "then": "Aquí es pot reciclar oli de cuina" - }, - "5": { - "then": "Aquí es pot reciclar oli de motor" - }, - "6": { - "then": "Aquí es poden reciclar tub fluroescents" - }, - "7": { - "then": "Aquí es poden reciclar residus verds" - }, - "8": { - "then": "Ací es poden reciclar residus orgànics" - }, - "9": { - "then": "Aquí es poden reciclar ampolles de vidre" } }, "question": "Què es pot reciclar aquí?" @@ -4562,12 +4552,6 @@ "1": { "then": "Aquest fanal utilitza LED" }, - "10": { - "then": "Aquest fanal utilitza làmpades de sodi d'alta pressió (taronja amb blanc)" - }, - "11": { - "then": "Aquest fanal s'il·lumina amb gas" - }, "2": { "then": "Aquest fanal utilitza il·luminació incandescent" }, @@ -4591,6 +4575,12 @@ }, "9": { "then": "Aquest fanal utilitza làmpades de sodi de baixa pressió (taronja monocroma)" + }, + "10": { + "then": "Aquest fanal utilitza làmpades de sodi d'alta pressió (taronja amb blanc)" + }, + "11": { + "then": "Aquest fanal s'il·lumina amb gas" } }, "question": "Quin tipus d'il·luminació utilitza aquest fanal?" @@ -5299,4 +5289,4 @@ } } } -} +} \ No newline at end of file diff --git a/langs/layers/cs.json b/langs/layers/cs.json index be98d33221..a436b9d7ec 100644 --- a/langs/layers/cs.json +++ b/langs/layers/cs.json @@ -35,16 +35,6 @@ "1": { "title": "volně stojící plakátovací skříň" }, - "10": { - "description": "Používá se pro reklamní nápisy, neonové nápisy, loga a vstupní nápisy institucí", - "title": "cedule" - }, - "11": { - "title": "socha" - }, - "12": { - "title": "nástěnná malba" - }, "2": { "title": "plakátovací skříň připevněná na stěnu" }, @@ -71,6 +61,16 @@ }, "9": { "title": "totem" + }, + "10": { + "description": "Používá se pro reklamní nápisy, neonové nápisy, loga a vstupní nápisy institucí", + "title": "cedule" + }, + "11": { + "title": "socha" + }, + "12": { + "title": "nástěnná malba" } }, "tagRenderings": { @@ -165,9 +165,6 @@ "1": { "then": "Toto je deska" }, - "10": { - "then": "Toto je nástěnná malba" - }, "2": { "then": "Toto je sloup" }, @@ -191,6 +188,9 @@ }, "9": { "then": "Toto je totem" + }, + "10": { + "then": "Toto je nástěnná malba" } }, "question": "O jaký typ reklamního prvku se jedná?", @@ -205,9 +205,6 @@ "1": { "then": "Deska" }, - "10": { - "then": "Nástěnná malba" - }, "2": { "then": "Skříň na plakáty" }, @@ -231,6 +228,9 @@ }, "9": { "then": "Totem" + }, + "10": { + "then": "Nástěnná malba" } } } @@ -309,15 +309,6 @@ "1": { "then": "Nástěnná malba" }, - "10": { - "then": "Azulejo (španělské dekorativní dlaždice)" - }, - "11": { - "then": "Obklady a dlažba" - }, - "12": { - "then": "Dřevořezba" - }, "2": { "then": "Malba" }, @@ -341,6 +332,15 @@ }, "9": { "then": "Reliéf" + }, + "10": { + "then": "Azulejo (španělské dekorativní dlaždice)" + }, + "11": { + "then": "Obklady a dlažba" + }, + "12": { + "then": "Dřevořezba" } }, "question": "Jaký je typ tohoto uměleckého díla?", @@ -1049,11 +1049,6 @@ } }, "question": "Jsou zde nástroje na opravu vlastního kola?" - }, - "opening_hours": { - "override": { - "question": "Kdy byla tato cyklistická kavárna otevřena?" - } } }, "title": { @@ -1502,4 +1497,4 @@ "walls_and_buildings": { "description": "Speciální zabudovaná vrstva poskytující všechny stěny a budovy. Tato vrstva je užitečná v předvolbách pro objekty, které lze umístit ke stěnám (např. AED, poštovní schránky, vchody, adresy, bezpečnostní kamery, …). Tato vrstva je ve výchozím nastavení neviditelná a uživatel ji nemůže přepínat." } -} +} \ No newline at end of file diff --git a/langs/layers/en.json b/langs/layers/en.json index 651532ac8f..a226100c0c 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -290,6 +290,9 @@ "presets": { "0": { "title": "an artwork" + }, + "1": { + "title": "an artwork on a wall" } }, "tagRenderings": { diff --git a/langs/layers/es.json b/langs/layers/es.json index 1e145bf088..962120a645 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -35,16 +35,6 @@ "1": { "title": "un mupi" }, - "10": { - "description": "Se utiliza para carteles publicitarios, letreros de neón, logotipos y carteles en entradas institucionales", - "title": "un lletrer" - }, - "11": { - "title": "una escultura" - }, - "12": { - "title": "una pared pintada" - }, "2": { "title": "un mupi sobre la pared" }, @@ -71,6 +61,16 @@ }, "9": { "title": "un tótem" + }, + "10": { + "description": "Se utiliza para carteles publicitarios, letreros de neón, logotipos y carteles en entradas institucionales", + "title": "un lletrer" + }, + "11": { + "title": "una escultura" + }, + "12": { + "title": "una pared pintada" } }, "tagRenderings": { @@ -165,9 +165,6 @@ "1": { "then": "Esto es un tablón de anuncios" }, - "10": { - "then": "Esto es una pared pintada" - }, "2": { "then": "Esto es una columna" }, @@ -191,6 +188,9 @@ }, "9": { "then": "Esto es un tótem" + }, + "10": { + "then": "Esto es una pared pintada" } }, "question": "¿Qué tipo de elemento publicitario es?", @@ -205,9 +205,6 @@ "1": { "then": "Tablon de anuncios" }, - "10": { - "then": "Pared Pintada" - }, "2": { "then": "Mupi" }, @@ -231,6 +228,9 @@ }, "9": { "then": "Tótem" + }, + "10": { + "then": "Pared Pintada" } } } @@ -309,12 +309,6 @@ "1": { "then": "Mural" }, - "10": { - "then": "Azulejo (Baldosas decorativas Españolas y Portuguesas)" - }, - "11": { - "then": "Cerámica" - }, "2": { "then": "Pintura" }, @@ -338,6 +332,12 @@ }, "9": { "then": "Relieve" + }, + "10": { + "then": "Azulejo (Baldosas decorativas Españolas y Portuguesas)" + }, + "11": { + "then": "Cerámica" } }, "question": "¿Qué tipo de obra es esta pieza?", @@ -1424,27 +1424,6 @@ "0": { "question": "Todos los conectores" }, - "10": { - "question": "Tiene un conector
Tipo 2 con cable (mennekes)
" - }, - "11": { - "question": "Tiene un conector
Tesla Supercharger CCS (un tipo2_css de marca)
" - }, - "12": { - "question": "Tiene un conector
Tesla Supercharger (destination)
" - }, - "13": { - "question": "Tiene un conector
Tesla Supercharger (Destination) (Tipo2 A con un cable de marca tesla)
" - }, - "14": { - "question": "Tiene un conector
USB para cargar teléfonos y dispositivos electrónicos pequeños
" - }, - "15": { - "question": "Tiene un conector
Bosch Active Connect con 3 pines y cable
" - }, - "16": { - "question": "Tiene un conector
Bosch Active Connect con 5 pines y cable
" - }, "2": { "question": "Tiene un conector
enchufe de pared Europeo con un pin de tierra (CEE7/4 tipo E
" }, @@ -1468,6 +1447,27 @@ }, "9": { "question": "Tiene un conector
Tipo 2 CCS (mennekes)
" + }, + "10": { + "question": "Tiene un conector
Tipo 2 con cable (mennekes)
" + }, + "11": { + "question": "Tiene un conector
Tesla Supercharger CCS (un tipo2_css de marca)
" + }, + "12": { + "question": "Tiene un conector
Tesla Supercharger (destination)
" + }, + "13": { + "question": "Tiene un conector
Tesla Supercharger (Destination) (Tipo2 A con un cable de marca tesla)
" + }, + "14": { + "question": "Tiene un conector
USB para cargar teléfonos y dispositivos electrónicos pequeños
" + }, + "15": { + "question": "Tiene un conector
Bosch Active Connect con 3 pines y cable
" + }, + "16": { + "question": "Tiene un conector
Bosch Active Connect con 5 pines y cable
" } } } @@ -1522,6 +1522,30 @@ "1": { "then": "Enchufe de pared Schuko sin pin de tierra (CEE7/4 tipo F)" }, + "2": { + "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" + }, + "3": { + "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" + }, + "4": { + "then": "Chademo" + }, + "5": { + "then": "Chademo" + }, + "6": { + "then": "Tipo 1 con cable (J1772)" + }, + "7": { + "then": "Tipo 1 con cable (J1772)" + }, + "8": { + "then": "Tipo 1 sin cable (J1772)" + }, + "9": { + "then": "Tipo 1 sin cable (J1772)" + }, "10": { "then": "CSS Tipo 1 (también conocido como Tipo 1 Combo)" }, @@ -1552,9 +1576,6 @@ "19": { "then": "Tipo 2 con cable (mennekes)" }, - "2": { - "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" - }, "20": { "then": "CCS Supercargador Tesla (un tipo2_css con marca)" }, @@ -1585,32 +1606,11 @@ "29": { "then": "Bosch Active Connect con 3 pines y cable" }, - "3": { - "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" - }, "30": { "then": "Bosch Active Connect con 5 pines y cable" }, "31": { "then": "Bosch Active Connect con 5 pines y cable" - }, - "4": { - "then": "Chademo" - }, - "5": { - "then": "Chademo" - }, - "6": { - "then": "Tipo 1 con cable (J1772)" - }, - "7": { - "then": "Tipo 1 con cable (J1772)" - }, - "8": { - "then": "Tipo 1 sin cable (J1772)" - }, - "9": { - "then": "Tipo 1 sin cable (J1772)" } }, "question": "¿Qué tipo de conexiones de carga están disponibles aquí?" @@ -2005,12 +2005,6 @@ "1": { "then": "Este carril bici está pavimentado" }, - "10": { - "then": "Este carril bici está hecho de gravilla" - }, - "12": { - "then": "Este carril bici está hecho de tierra natural" - }, "2": { "then": "Este carril bici está hecho de asfalto" }, @@ -2025,6 +2019,12 @@ }, "9": { "then": "Este carril bici está hecho de grava" + }, + "10": { + "then": "Este carril bici está hecho de gravilla" + }, + "12": { + "then": "Este carril bici está hecho de tierra natural" } }, "question": "¿De qué superficie está hecho este carril bici?", @@ -2070,9 +2070,6 @@ "1": { "then": "Este carril bici está pavimentado" }, - "10": { - "then": "Este carril bici está hecho de gravilla" - }, "2": { "then": "Este carril bici está hecho de asfalto" }, @@ -2084,6 +2081,9 @@ }, "9": { "then": "Este carril bici está hecho de grava" + }, + "10": { + "then": "Este carril bici está hecho de gravilla" } }, "question": "¿De qué esta hecha la superficie de esta calle?", @@ -2616,18 +2616,6 @@ "0": { "then": "Esto es una pizzería" }, - "10": { - "then": "Aquí se sirven platos Chinos" - }, - "11": { - "then": "Aquí se sirven platos Griegos" - }, - "12": { - "then": "Aquí se sirven platos Indios" - }, - "13": { - "then": "Aquí se sirven platos Turcos" - }, "2": { "then": "Principalmente sirve pasta" }, @@ -2648,6 +2636,18 @@ }, "9": { "then": "Aquí se sirven platos Franceses" + }, + "10": { + "then": "Aquí se sirven platos Chinos" + }, + "11": { + "then": "Aquí se sirven platos Griegos" + }, + "12": { + "then": "Aquí se sirven platos Indios" + }, + "13": { + "then": "Aquí se sirven platos Turcos" } }, "question": "¿Qué comida se sirve aquí?", @@ -3045,19 +3045,6 @@ } } }, - "10": { - "options": { - "0": { - "question": "Todas las notas" - }, - "1": { - "question": "Ocultar las nostras de importación" - }, - "2": { - "question": "Solo mostrar las notas de importación" - } - } - }, "2": { "options": { "0": { @@ -3113,6 +3100,19 @@ "question": "Solo mostrar las notas abiertas" } } + }, + "10": { + "options": { + "0": { + "question": "Todas las notas" + }, + "1": { + "question": "Ocultar las nostras de importación" + }, + "2": { + "question": "Solo mostrar las notas de importación" + } + } } }, "name": "Notas de OpenStreetMap", @@ -3415,6 +3415,21 @@ "1": { "question": "Reciclaje de baterías" }, + "3": { + "question": "Reciclaje de latas" + }, + "4": { + "question": "Reciclaje de ropa" + }, + "5": { + "question": "Reciclaje de aceite de cocina" + }, + "6": { + "question": "Reciclaje de aceite de motor" + }, + "9": { + "question": "Reciclaje de botellas de cristal" + }, "10": { "question": "Reciclaje de cristal" }, @@ -3438,21 +3453,6 @@ }, "18": { "question": "Reciclaje de pequeños electrodomésticos" - }, - "3": { - "question": "Reciclaje de latas" - }, - "4": { - "question": "Reciclaje de ropa" - }, - "5": { - "question": "Reciclaje de aceite de cocina" - }, - "6": { - "question": "Reciclaje de aceite de motor" - }, - "9": { - "question": "Reciclaje de botellas de cristal" } } } @@ -3495,6 +3495,24 @@ "0": { "then": "Aquí se pueden reciclar baterías" }, + "2": { + "then": "Aquí se pueden reciclar latas" + }, + "3": { + "then": "Aquí se puede reciclar ropa" + }, + "4": { + "then": "Aquí se puede reciclar aceite de cocina" + }, + "5": { + "then": "Aquí se puede reciclar aceite de motor" + }, + "8": { + "then": "Aquí se pueden reciclar residuos orgánicos" + }, + "9": { + "then": "Aquí se pueden reciclar botellas de cristal" + }, "10": { "then": "Aquí se puede reciclar cristal" }, @@ -3518,24 +3536,6 @@ }, "19": { "then": "Aquí se pueden reciclar zapatos" - }, - "2": { - "then": "Aquí se pueden reciclar latas" - }, - "3": { - "then": "Aquí se puede reciclar ropa" - }, - "4": { - "then": "Aquí se puede reciclar aceite de cocina" - }, - "5": { - "then": "Aquí se puede reciclar aceite de motor" - }, - "8": { - "then": "Aquí se pueden reciclar residuos orgánicos" - }, - "9": { - "then": "Aquí se pueden reciclar botellas de cristal" } }, "question": "¿Qué se puede reciclar aquí?" @@ -3817,6 +3817,11 @@ "question": "¿De qué color es la luz que emite esta lámpara?", "render": "Esta lámpara emite luz {light:colour}" }, + "count": { + "mappings": { + "0": {} + } + }, "direction": { "question": "¿Hacia donde apunta esta lámpara?", "render": "Esta lámpara apunta hacia {light:direction}" @@ -3857,12 +3862,6 @@ "1": { "then": "Esta lámpara utiliza LEDs" }, - "10": { - "then": "Esta lámpara utiliza lámparas de sodio de alta presión (naranja con blanco)" - }, - "11": { - "then": "Esta lampara se ilumina con gas" - }, "2": { "then": "Esta lámpara utiliza iluminación incandescente" }, @@ -3883,6 +3882,12 @@ }, "9": { "then": "Esta lámpara utiliza lámparas de sodio de baja presión (naranja monocromo)" + }, + "10": { + "then": "Esta lámpara utiliza lámparas de sodio de alta presión (naranja con blanco)" + }, + "11": { + "then": "Esta lampara se ilumina con gas" } }, "question": "¿Qué tipo de iluminación utiliza esta lámpara?" @@ -4329,4 +4334,4 @@ } } } -} +} \ No newline at end of file diff --git a/langs/layers/fr.json b/langs/layers/fr.json index 7fd5baf7a3..8cc580250a 100644 --- a/langs/layers/fr.json +++ b/langs/layers/fr.json @@ -29,16 +29,6 @@ "0": { "description": "Un grand équipement extérieur, principalement disposé dans les zones à fort trafic comme une route" }, - "10": { - "description": "Désigne une enseigne publicitaire, une enseigne néon, les logos ou des indications d'entrées", - "title": "une enseigne" - }, - "11": { - "title": "une sculpture" - }, - "12": { - "title": "une peinture murale" - }, "3": { "description": "Petit panneau pour l’affichage de proximité, généralement à destination des piétons", "title": "un petit panneau" @@ -61,6 +51,16 @@ }, "9": { "title": "un totem" + }, + "10": { + "description": "Désigne une enseigne publicitaire, une enseigne néon, les logos ou des indications d'entrées", + "title": "une enseigne" + }, + "11": { + "title": "une sculpture" + }, + "12": { + "title": "une peinture murale" } }, "tagRenderings": { @@ -143,9 +143,6 @@ "1": { "then": "C'est un petit panneau" }, - "10": { - "then": "C'est une peinture murale" - }, "2": { "then": "C'est une colonne" }, @@ -169,6 +166,9 @@ }, "9": { "then": "C'est un totem" + }, + "10": { + "then": "C'est une peinture murale" } }, "question": "De quel type de dispositif publicitaire s'agit-il ?" @@ -179,9 +179,6 @@ "1": { "then": "Petit panneau" }, - "10": { - "then": "Peinture murale" - }, "3": { "then": "Colonne" }, @@ -202,6 +199,9 @@ }, "9": { "then": "Totem" + }, + "10": { + "then": "Peinture murale" } } } @@ -280,15 +280,6 @@ "1": { "then": "Peinture murale" }, - "10": { - "then": "Azulejo (faïence latine)" - }, - "11": { - "then": "Carrelage" - }, - "12": { - "then": "Sculpture sur bois" - }, "2": { "then": "Peinture" }, @@ -312,6 +303,15 @@ }, "9": { "then": "Relief" + }, + "10": { + "then": "Azulejo (faïence latine)" + }, + "11": { + "then": "Carrelage" + }, + "12": { + "then": "Sculpture sur bois" } }, "question": "Quel est le type de cette œuvre d'art ?", @@ -2279,15 +2279,6 @@ "1": { "then": "Cette piste cyclable est goudronée" }, - "10": { - "then": "Cette piste cyclable est faite en graviers fins" - }, - "11": { - "then": "Cette piste cyclable est en cailloux" - }, - "12": { - "then": "Cette piste cyclable est faite en sol brut" - }, "2": { "then": "Cette piste cyclable est asphaltée" }, @@ -2311,6 +2302,15 @@ }, "9": { "then": "Cette piste cyclable est faite en graviers" + }, + "10": { + "then": "Cette piste cyclable est faite en graviers fins" + }, + "11": { + "then": "Cette piste cyclable est en cailloux" + }, + "12": { + "then": "Cette piste cyclable est faite en sol brut" } }, "question": "De quoi est faite la surface de la piste cyclable ?", @@ -2359,15 +2359,6 @@ "1": { "then": "Cette piste cyclable est pavée" }, - "10": { - "then": "Cette piste cyclable est faite en graviers fins" - }, - "11": { - "then": "Cette piste cyclable est en cailloux" - }, - "12": { - "then": "Cette piste cyclable est faite en sol brut" - }, "2": { "then": "Cette piste cyclable est asphaltée" }, @@ -2391,6 +2382,15 @@ }, "9": { "then": "Cette piste cyclable est faite en graviers" + }, + "10": { + "then": "Cette piste cyclable est faite en graviers fins" + }, + "11": { + "then": "Cette piste cyclable est en cailloux" + }, + "12": { + "then": "Cette piste cyclable est faite en sol brut" } }, "question": "De quel materiel est faite cette rue ?", @@ -3213,21 +3213,6 @@ "1": { "then": "C'est une friterie" }, - "10": { - "then": "Des plats chinois sont servis ici" - }, - "11": { - "then": "Des plats grecs sont servis ici" - }, - "12": { - "then": "Des plats indiens sont servis ici" - }, - "13": { - "then": "Des plats turcs sont servis ici" - }, - "14": { - "then": "Des plats thaïlandais sont servis ici" - }, "2": { "then": "Restaurant Italien" }, @@ -3251,6 +3236,21 @@ }, "9": { "then": "Des plats français sont servis ici" + }, + "10": { + "then": "Des plats chinois sont servis ici" + }, + "11": { + "then": "Des plats grecs sont servis ici" + }, + "12": { + "then": "Des plats indiens sont servis ici" + }, + "13": { + "then": "Des plats turcs sont servis ici" + }, + "14": { + "then": "Des plats thaïlandais sont servis ici" } }, "question": "Quelle type de nourriture est servie ici ?", @@ -5482,4 +5482,4 @@ } } } -} +} \ No newline at end of file diff --git a/langs/layers/nl.json b/langs/layers/nl.json index b50bb10c2f..968ae958fc 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -177,6 +177,9 @@ "presets": { "0": { "title": "een kunstwerk" + }, + "1": { + "title": "een kunstwerk op een muur" } }, "tagRenderings": { diff --git a/langs/layers/pt_BR.json b/langs/layers/pt_BR.json index 1be173acff..fd2941aa29 100644 --- a/langs/layers/pt_BR.json +++ b/langs/layers/pt_BR.json @@ -585,4 +585,4 @@ "render": "Bicicleta fantasma" } } -} +} \ No newline at end of file diff --git a/langs/nb_NO.json b/langs/nb_NO.json index d3b6fc5af7..c8fb04fd5c 100644 --- a/langs/nb_NO.json +++ b/langs/nb_NO.json @@ -71,7 +71,6 @@ }, "general": { "about": "Rediger og legg til OpenStreetMap for et gitt tema", - "aboutMapcomplete": "

Om

Bruk MapComplete til å legge til OpenStreetMap-info i ett tema. Besvar spørsmål og få endringene vist i løpet av minutter. I de fleste temaene kan du legge inn bilder eller legge igjen en vurdering. Temavedlikeholderen definerer elementer, spørsmål og språk for det.

Finn ut mer

MapComplete tilbyr alltid neste steg for å lære mer om OpenStreetMap.

  • Når bygd inn på en nettside lenker iframe-elementet til en fullskjermsversjon av MapComplete.
  • Fullskjermsversjonen tilbyr info om OpenStreetMap.
  • Visning fungerer uten innlogging, men redigering krever en OSM-konto.
  • Hvis du ikke er innlogget blir du spurt om å gjøre det.
  • Når du har besvart ett spørsmål, kan du legge til nye funksjoner på kartet.
  • Etter en stund vil OSM-etiketter bli vist, som i sin tur lenker til wiki-en.


Har du oppdaget et problem? Har du en funksjonsforespørsel? Vil du bistå oversettelsen? Gå til kildekoden eller problemsporeren.

Vil du se din framdrift? Følg redigeringsantallet på OsmCha.

", "add": { "addNew": "Legg til {category} her", "backToSelect": "Velg en annen kategori", diff --git a/langs/nl.json b/langs/nl.json index cd7cba7904..3635b3c948 100644 --- a/langs/nl.json +++ b/langs/nl.json @@ -88,7 +88,6 @@ }, "general": { "about": "Bewerk en voeg data toe aan OpenStreetMap over een specifiek onderwerp op een gemakkelijke manier", - "aboutMapcomplete": "

Over MapComplete

Met MapComplete kun je OpenStreetMap verrijken met informatie over een bepaald thema. Beantwoord enkele vragen, en binnen een paar minuten is jouw bijdrage wereldwijd beschikbaar! In de meeste thema's kan je foto's toevoegen of zelfs een review achterlaten. De maker van het thema bepaalt de elementen, vragen en taalversies voor het thema.

Ontdek meer

MapComplete biedt altijd de volgende stap naar meer OpenStreetMap:

  • Indien ingebed in een website linkt het iframe naar de volledige MapComplete
  • De volledige versie heeft uitleg over OpenStreetMap
  • Bekijken kan altijd, maar wijzigen vereist een OSM-account
  • Als je niet aangemeld bent, wordt je gevraagd dit te doen
  • Als je minstens één vraag hebt beantwoord, kan je ook elementen toevoegen
  • Heb je genoeg changesets, dan verschijnen de OSM-tags, nog later links naar de wiki

Merk je een bug of wil je een extra feature? Wil je helpen vertalen? Bezoek dan de broncode en issue tracker.

Wil je je vorderingen zien? Volg de edits op OsmCha.

", "add": { "addNew": "Voeg {category} toe", "backToSelect": "Selecteer een andere categorie", @@ -176,7 +175,6 @@ "error": "Er ging iets mis", "example": "Voorbeeld", "examples": "Voorbeelden", - "feelFreeToSkip": "Je kan hieronder nog informatie toevoegen of updaten, maar sla gerust vragen over waarvoor je het antwoord niet kent.", "fewChangesBefore": "Gelieve eerst enkele vragen van bestaande objecten te beantwoorden vooraleer zelf objecten toe te voegen.", "getStartedLogin": "Login met OpenStreetMap om te beginnen", "getStartedNewAccount": " of maak een nieuwe account aan", @@ -214,7 +212,6 @@ "streetcomplete": "Een andere, gelijkaardige Android applicatie is StreetComplete." }, "nameInlineQuestion": "De naam van dit {category} is $$$", - "newlyCreated": "Je hebt dit punt net toegevoegd! Bedankt om deze info met iedereen te delen en om de mensen wereldwijd te helpen.", "next": "Volgende", "noMatchingMapping": "Geen overeenkomsten gevonden…", "noNameCategory": "{category} zonder naam", diff --git a/langs/pl.json b/langs/pl.json index 744b857c6a..4cf22a22b9 100644 --- a/langs/pl.json +++ b/langs/pl.json @@ -22,7 +22,6 @@ }, "general": { "about": "Łatwo edytuj i dodaj OpenStreetMap dla określonego motywu", - "aboutMapcomplete": "

O MapComplete

Dzięki MapComplete możesz wzbogacić OpenStreetMap o informacje na pojedynczy temat. Odpowiedz na kilka pytań, a w ciągu kilku minut Twój wkład będzie dostępny na całym świecie! Opiekun motywu definiuje elementy, pytania i języki dla tematu.

Dowiedz się więcej

MapComplete zawsze oferuje następny krok, by dowiedzieć się więcej o OpenStreetMap.

  • Po osadzeniu na stronie internetowej, iframe łączy się z pełnoekranowym MapComplete
  • Wersja pełnoekranowa oferuje informacje o OpenStreetMap
  • Przeglądanie działa bez logowania, ale edycja wymaga loginu OSM.
  • Jeżeli nie jesteś zalogowany, zostaniesz poproszony o zalogowanie się
  • Po udzieleniu odpowiedzi na jedno pytanie, możesz dodać nowe punkty do mapy
  • Po chwili wyświetlane są rzeczywiste tagi OSM, które później linkują do wiki


Zauważyłeś problem? Czy masz prośbę o dodanie jakiejś funkcji? Chcesz pomóc w tłumaczeniu? Udaj się do kodu źródłowego lub issue trackera.

Chcesz zobaczyć swoje postępy? Śledź liczbę edycji na OsmCha.

.", "add": { "addNew": "Dodaj nową {category} tutaj", "confirmButton": "Dodaj tutaj {category}.
Twój dodatek jest widoczny dla wszystkich
", diff --git a/langs/pt.json b/langs/pt.json index bf9137b8dd..7859df7dcf 100644 --- a/langs/pt.json +++ b/langs/pt.json @@ -41,7 +41,6 @@ }, "general": { "about": "Edite e adicione facilmente o OpenStreetMap para um determinado tema", - "aboutMapcomplete": "

Sobre

Use o MapComplete para adicionar informações ao OpenStreetMap sobre um tema específico. Responda a perguntas e em poucos minutos as suas contribuições estão disponíveis em todos os lugares. Na maioria dos temas pode adicionar imagens ou mesmo deixar uma avaliação. O responsável pelo tema define os elementos, as perguntas e os idiomas disponíveis nele.

Descubra mais

O MapComplete mostra sempre o próximo passo para saber mais sobre o OpenStreetMap.

  • Quando incorporado num site, o iframe liga-se ao MapComplete em ecrã cheio.
  • A versão ecrã cheio fornece informações sobre o OpenStreetMap
  • A visualização funciona sem ser preciso autenticar-se, mas a edição requer uma conta no OpenStreetMap.
  • Se não estiver autenticado, é solicitado a fazê-lo
  • Após responder a uma pergunta, pode adicionar novos elementos ao mapa
  • Depois de um tempo, as etiquetas reais do OpenStreetMap são mostradas, mais tarde vinculando-se à wiki


Deparou-se com um problema? Quer uma nova funcionalidade? Quer ajudar a traduzir? Vá ao código-fonte ou rastreador de problemas.

Quer ver o seu progresso? Veja a contagem de edições em OsmCha.

", "add": { "addNew": "Adicionar {category} aqui", "confirmButton": "Adicione uma {category} aqui.
Esta adição será visível a todos
", diff --git a/langs/pt_BR.json b/langs/pt_BR.json index e23085d2ed..d6a3292eb3 100644 --- a/langs/pt_BR.json +++ b/langs/pt_BR.json @@ -30,7 +30,6 @@ }, "general": { "about": "Edite e adicione facilmente o OpenStreetMap para um determinado tema", - "aboutMapcomplete": "

Sobre

Use o MapComplete para adicionar informações ao OpenStreetMap sobre um tema específico. Responda a algumas perguntas e, em poucos minutos, suas contribuições estarão disponíveis em todos os lugares. Na maioria dos temas você pode adicionar fotos ou até mesmo deixar uma avaliação. O mantenedor do tema define os elementos, questões e idiomas disponíveis para ele.

Descubra mais

O MapComplete sempre mostra a próxima etapa para aprender mais sobre o OpenStreetMap.

  • Quando incorporada em um site, o iframe vincula-se a um MapComplete em tela inteira.
  • A versão em tela inteira oferece informações sobre o OpenStreetMap.
  • A visualização funciona sem login, mas a edição exige uma conta no OSM.
  • Se você não estiver conectado, será solicitado que você faça o login
  • Depois de responder a uma pergunta, você pode adicionar novos elementos no mapa
  • Depois de um tempo as tags OSM reais são mostradas e posteriormente vinculadas à wiki


Encontrou um problema? Tem uma solicitação de novo recurso? Quer ajudar a traduzir? Acesse o código-fonte ou o rastreador de problemas.

Quer ver seu progresso? Siga o contador de edições no OsmCha.

", "add": { "addNew": "Adicione {category} aqui", "confirmButton": "Adicione uma {category} aqui.
Sua adição é visível para todos
", diff --git a/langs/ru.json b/langs/ru.json index 35538e08ed..03b297b6ad 100644 --- a/langs/ru.json +++ b/langs/ru.json @@ -29,7 +29,6 @@ }, "general": { "about": "С лёгкостью редактируйте и дополняйте OpenStreetMap на определённую тему", - "aboutMapcomplete": "

О MapComplete

С помощью MapComplete вы можете обогатить OpenStreetMap информацией по одной теме. Ответьте на несколько вопросов, и через несколько минут ваши материалы будут доступны по всему миру! Сопровождающий темы определяет элементы, вопросы и языки для темы.

Узнайте больше

MapComplete всегда предлагает следующий шаг, чтобы узнать больше об OpenStreetMap.

  • При встраивании в веб-сайт iframe ссылается на полноэкранную версию MapComplete
  • Полноэкранная версия предлагает информацию об OpenStreetMap
  • Просмотр работает без входа, но для редактирования требуется вход в OSM.
  • Если вы не вошли в систему, вас попросят войти
  • Ответив на один вопрос, вы можете добавлять новые точки на карту
  • Через некоторое время отображаются актуальные OSM-метки с последующей ссылкой на вики


Вы заметили проблему? У вас есть запрос на функциональность? Хотите помочь с переводом? Зайдите на репозиторий с исходным кодом или трекер проблем.

Хотите увидеть свой прогресс? Следите за количеством правок на OsmCha.

", "add": { "addNew": "Добавить новую {category} здесь", "confirmButton": "Добавить {category} сюда.
Ваш вклад будет виден каждому
", diff --git a/langs/shared-questions/ca.json b/langs/shared-questions/ca.json index 053804a07a..9e8db263ac 100644 --- a/langs/shared-questions/ca.json +++ b/langs/shared-questions/ca.json @@ -307,4 +307,4 @@ "question": "Quina és la correspondent entitat a Wikidata?" } } -} +} \ No newline at end of file diff --git a/langs/shared-questions/es.json b/langs/shared-questions/es.json index 62d1b3c05c..8205a7733a 100644 --- a/langs/shared-questions/es.json +++ b/langs/shared-questions/es.json @@ -307,4 +307,4 @@ "question": "¿Cual es la entidad de Wikidata que se corresponde?" } } -} +} \ No newline at end of file diff --git a/langs/themes/ca.json b/langs/themes/ca.json index 668816cb17..9d400c28cf 100644 --- a/langs/themes/ca.json +++ b/langs/themes/ca.json @@ -475,7 +475,7 @@ } }, "overrideAll": { - "tagRenderings+": { + "+tagRenderings": { "0": { "mappings": { "0": { @@ -771,108 +771,6 @@ "description": "Un mapa amb voreres i encreuaments.", "title": "Vorals i encreuaments" }, - "mapcomplete-changes": { - "description": "Aquest mapa mostra tots els canvis fets amb MapComplete", - "layers": { - "0": { - "description": "Mostra tots els canvis de MapComplete", - "filter": { - "0": { - "options": { - "0": { - "question": "El nom del tema conté {search}" - } - } - }, - "1": { - "options": { - "0": { - "question": "Fet pel col·laborador {search}" - } - } - }, - "2": { - "options": { - "0": { - "question": "No fet pel col·laborador {search}" - } - } - }, - "3": { - "options": { - "0": { - "question": "Fet abans de {search}" - } - } - }, - "4": { - "options": { - "0": { - "question": "Fet després de {search}" - } - } - }, - "5": { - "options": { - "0": { - "question": "Idioma de l'usuari (codi iso) {search}" - } - } - }, - "6": { - "options": { - "0": { - "question": "Fet amb l'amfitrió {search}" - } - } - }, - "7": { - "options": { - "0": { - "question": "El conjunt de canvis ha afegit almenys una imatge" - } - } - } - }, - "name": "Centre del conjunt de canvis", - "tagRenderings": { - "contributor": { - "question": "Quin col·laborador va fer aquest canvi?", - "render": "Canvi fet per {user}" - }, - "host": { - "question": "Amb quin amfitrió (lloc web) es va fer aquest canvi?", - "render": "Canvi amb {host}" - }, - "locale": { - "question": "Amb quina configuració regional (idioma) s'ha fet aquest canvi?", - "render": "La configuració regional de l'usuari és {locale}" - }, - "show_changeset_id": { - "render": "Conjunt de canvi {id}" - }, - "theme-id": { - "question": "Quin tema es va utilitzar per fer aquest canvi?", - "render": "Canvi amb el tema {theme}" - } - }, - "title": { - "render": "Conjunt de canvis per a {theme}" - } - }, - "1": { - "override": { - "tagRenderings": { - "link_to_more": { - "render": "Es poden trobar més estadístiques aquí" - } - } - } - } - }, - "shortDescription": "Mostra els canvis fets per MapComplete", - "title": "Canvis fets amb MapComplete" - }, "maproulette": { "description": "Tema que mostra les tasques de MapRoulette, que us permet cercar-les, filtrar-les i solucionar-les.", "title": "Tasques de MapRoulette" diff --git a/langs/themes/cs.json b/langs/themes/cs.json index 71c246feb5..c7c55ced85 100644 --- a/langs/themes/cs.json +++ b/langs/themes/cs.json @@ -475,7 +475,7 @@ } }, "overrideAll": { - "tagRenderings+": { + "+tagRenderings": { "0": { "mappings": { "0": { @@ -771,108 +771,6 @@ "description": "Mapa zobrazující obrubníky a přechody.", "title": "Obrubníky a přechody" }, - "mapcomplete-changes": { - "description": "Tyto mapy zobrazují všechny změny provedené pomocí MapComplete", - "layers": { - "0": { - "description": "Zobrazí všechny změny MapComplete", - "filter": { - "0": { - "options": { - "0": { - "question": "Themename obsahuje {search}" - } - } - }, - "1": { - "options": { - "0": { - "question": "Vytvořil přispěvatel {search}" - } - } - }, - "2": { - "options": { - "0": { - "question": "Ne vytvořeno přispěvatelem {search}" - } - } - }, - "3": { - "options": { - "0": { - "question": "Vytvořeno před {search}" - } - } - }, - "4": { - "options": { - "0": { - "question": "Vytvořeno po {search}" - } - } - }, - "5": { - "options": { - "0": { - "question": "Jazyk uživatele (iso-kód) {search}" - } - } - }, - "6": { - "options": { - "0": { - "question": "Vyrobeno u hostitele {search}" - } - } - }, - "7": { - "options": { - "0": { - "question": "Sada změn přidala alespoň jeden obrázek" - } - } - } - }, - "name": "Centra změn", - "tagRenderings": { - "contributor": { - "question": "Který přispěvatel tuto změnu provedl?", - "render": "Změna provedená {user}" - }, - "host": { - "question": "U jakého hostitele (webové stránky) byla tato změna provedena?", - "render": "Změna u {host}" - }, - "locale": { - "question": "V jakém prostředí (jazyce) byla tato změna provedena?", - "render": "Uživatelské prostředí je {locale}" - }, - "show_changeset_id": { - "render": "Sada změn je {id}" - }, - "theme-id": { - "question": "Jaké téma bylo použito k provedení této změny?", - "render": "Změna pomocí tématu {theme}" - } - }, - "title": { - "render": "Sada změn pro {theme}" - } - }, - "1": { - "override": { - "tagRenderings": { - "link_to_more": { - "render": "Další statistiky lze nalézt zde" - } - } - } - } - }, - "shortDescription": "Zobrazuje změny provedené nástrojem MapComplete", - "title": "Změny provedené pomocí MapComplete" - }, "maproulette": { "description": "Téma zobrazující úkoly MapRoulette, které umožňuje vyhledávat, filtrovat a opravovat je.", "title": "Úkoly MapRoulette" @@ -893,7 +791,7 @@ "title": "Do přírody" }, "notes": { - "description": "Poznámka je špendlík na mapě s textem, jež označuje, že něco není v pořádku.

Nezapomeňte si prohlédnout zobrazení filtru pro vyhledávání uživatelů a textu.", + "description": "Poznámka je špendlík na mapě s textem, jež označuje, že něco není v pořádku.

Nezapomeňte si prohlédnout zobrazení filtru pro vyhledávání uživatelů a textu.", "title": "Poznámky k OpenStreetMap" }, "observation_towers": { @@ -904,33 +802,6 @@ "onwheels": { "description": "Na této mapě jsou zobrazena veřejně přístupná místa pro vozíčkáře, a lze je také snadno přidat", "layers": { - "19": { - "override": { - "=title": { - "render": "Statistiky" - } - } - }, - "20": { - "override": { - "+tagRenderings": { - "0": { - "render": { - "special": { - "text": "Import" - } - } - }, - "1": { - "render": { - "special": { - "message": "Přidat všechny navrhované značky" - } - } - } - } - } - }, "4": { "override": { "filter": { @@ -973,6 +844,33 @@ "override": { "name": "Parkovací místa pro osoby se zdravotním postižením" } + }, + "19": { + "override": { + "=title": { + "render": "Statistiky" + } + } + }, + "20": { + "override": { + "+tagRenderings": { + "0": { + "render": { + "special": { + "text": "Dovoz" + } + } + }, + "1": { + "render": { + "special": { + "message": "Přidat všechny navrhované značky" + } + } + } + } + } } }, "title": "OnWheels" @@ -996,7 +894,7 @@ "title": "Osobní téma" }, "pets": { - "description": "Na této mapě najdete různá zajímavá místa pro vaše domácí mazlíčky: veterináře, psí parky, obchody pro zvířata, restaurace vhodné pro vstup se psy, ...", + "description": "Na této mapě najdete různá zajímavá místa pro vaše domácí mazlíčky: veterináře, psí parky, obchody pro zvířata, restaurace pro psy, ...", "layers": { "1": { "override": { @@ -1133,6 +1031,10 @@ "stations": { "description": "Zobrazení, úprava a přidání podrobností o vlakovém nádraží", "layers": { + "3": { + "description": "Vrstva zobrazující vlaková nádraží", + "name": "Vlaková nádraží" + }, "15": { "description": "Zobrazuje vlaky odjíždějící z této stanice", "name": "Odjezdové tabule", @@ -1164,10 +1066,6 @@ "title": { "render": "Odjezdová tabule" } - }, - "3": { - "description": "Vrstva zobrazující vlaková nádraží", - "name": "Vlaková nádraží" } }, "title": "Vlaková nádraží" @@ -1259,4 +1157,4 @@ "shortDescription": "Mapa odpadkových košů", "title": "Odpadkový koš" } -} +} \ No newline at end of file diff --git a/langs/themes/da.json b/langs/themes/da.json index 35750cd9e3..c2c2f0cf4c 100644 --- a/langs/themes/da.json +++ b/langs/themes/da.json @@ -362,7 +362,7 @@ } }, "overrideAll": { - "tagRenderings+": { + "+tagRenderings": { "0": { "mappings": { "0": { diff --git a/langs/themes/de.json b/langs/themes/de.json index 547f498579..354535a492 100644 --- a/langs/themes/de.json +++ b/langs/themes/de.json @@ -475,7 +475,7 @@ } }, "overrideAll": { - "tagRenderings+": { + "+tagRenderings": { "0": { "mappings": { "0": { @@ -771,108 +771,6 @@ "description": "Eine Karte mit Bordsteinen und Überwegen.", "title": "Bordsteine und Überwege" }, - "mapcomplete-changes": { - "description": "Diese Karte zeigt alle mit MapComplete vorgenommenen Änderungen", - "layers": { - "0": { - "description": "Zeigt alle MapComplete-Änderungen", - "filter": { - "0": { - "options": { - "0": { - "question": "Themename enthält {search}" - } - } - }, - "1": { - "options": { - "0": { - "question": "Erstellt von {search}" - } - } - }, - "2": { - "options": { - "0": { - "question": "Nicht erstellt von {search}" - } - } - }, - "3": { - "options": { - "0": { - "question": "Erstellt vor {search}" - } - } - }, - "4": { - "options": { - "0": { - "question": "Erstellt nach {search}" - } - } - }, - "5": { - "options": { - "0": { - "question": "Benutzersprache (ISO-Code) {search}" - } - } - }, - "6": { - "options": { - "0": { - "question": "Erstellt mit host {search}" - } - } - }, - "7": { - "options": { - "0": { - "question": "Im Änderungssatz wurde mindestens ein Bild hinzugefügt" - } - } - } - }, - "name": "Zentrum der Änderungssätze", - "tagRenderings": { - "contributor": { - "question": "Wer hat diese Änderung vorgenommen?", - "render": "Änderung vorgenommen von {user}" - }, - "host": { - "question": "Über welchen Host (Webseite) wurde diese Änderung vorgenommen?", - "render": "Geändert über {host}" - }, - "locale": { - "question": "In welchem Gebietsschema (Sprache) wurde diese Änderung vorgenommen?", - "render": "Benutzergebietsschema ist {locale}" - }, - "show_changeset_id": { - "render": "Änderungssatz {id}" - }, - "theme-id": { - "question": "Welches Thema wurde für diese Änderung verwendet?", - "render": "Geändert mit Thema {theme}" - } - }, - "title": { - "render": "Änderungssatz für {theme}" - } - }, - "1": { - "override": { - "tagRenderings": { - "link_to_more": { - "render": "Weitere Statistiken hier" - } - } - } - } - }, - "shortDescription": "Zeigt Änderungen, die mit MapComplete vorgenommen wurden", - "title": "Änderungen mit MapComplete" - }, "maproulette": { "description": "Thema mit MapRoulette-Aufgaben, die Sie suchen, filtern und beheben können.", "title": "MapRoulette-Aufgaben" diff --git a/langs/themes/en.json b/langs/themes/en.json index aa15b52418..cbcf24c3a5 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -475,7 +475,7 @@ } }, "overrideAll": { - "tagRenderings+": { + "+tagRenderings": { "0": { "mappings": { "0": { diff --git a/langs/themes/es.json b/langs/themes/es.json index 3132722e8d..c19f464045 100644 --- a/langs/themes/es.json +++ b/langs/themes/es.json @@ -475,7 +475,7 @@ } }, "overrideAll": { - "tagRenderings+": { + "+tagRenderings": { "0": { "mappings": { "0": { diff --git a/langs/themes/fr.json b/langs/themes/fr.json index 91260060b1..b261740c9a 100644 --- a/langs/themes/fr.json +++ b/langs/themes/fr.json @@ -475,7 +475,7 @@ } }, "overrideAll": { - "tagRenderings+": { + "+tagRenderings": { "0": { "mappings": { "0": { diff --git a/langs/themes/hu.json b/langs/themes/hu.json index 9b421ebd91..aebf8de331 100644 --- a/langs/themes/hu.json +++ b/langs/themes/hu.json @@ -128,7 +128,7 @@ } }, "overrideAll": { - "tagRenderings+": { + "+tagRenderings": { "0": { "mappings": { "0": { diff --git a/langs/themes/it.json b/langs/themes/it.json index 6f01448b8b..7067d50f0f 100644 --- a/langs/themes/it.json +++ b/langs/themes/it.json @@ -356,7 +356,7 @@ } }, "overrideAll": { - "tagRenderings+": { + "+tagRenderings": { "0": { "mappings": { "0": { diff --git a/langs/themes/ja.json b/langs/themes/ja.json index 6fc5dd6277..008052c499 100644 --- a/langs/themes/ja.json +++ b/langs/themes/ja.json @@ -285,7 +285,7 @@ } }, "overrideAll": { - "tagRenderings+": { + "+tagRenderings": { "0": { "mappings": { "0": { diff --git a/langs/themes/nb_NO.json b/langs/themes/nb_NO.json index c33658efd5..a7a1dd0673 100644 --- a/langs/themes/nb_NO.json +++ b/langs/themes/nb_NO.json @@ -315,7 +315,7 @@ } }, "overrideAll": { - "tagRenderings+": { + "+tagRenderings": { "0": { "mappings": { "0": { diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 96a044ff20..ef83fe5d2f 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -475,7 +475,7 @@ } }, "overrideAll": { - "tagRenderings+": { + "+tagRenderings": { "0": { "mappings": { "0": { diff --git a/langs/themes/pa_PK.json b/langs/themes/pa_PK.json index d79643e756..5fcf26d148 100644 --- a/langs/themes/pa_PK.json +++ b/langs/themes/pa_PK.json @@ -117,7 +117,7 @@ } }, "overrideAll": { - "tagRenderings+": { + "+tagRenderings": { "1": { "mappings": { "2": { diff --git a/langs/zh_Hans.json b/langs/zh_Hans.json index a55217a87b..82771d62d7 100644 --- a/langs/zh_Hans.json +++ b/langs/zh_Hans.json @@ -28,7 +28,6 @@ "reload": "重新加载数据" }, "general": { - "aboutMapcomplete": "

关于MapComplete

使用它在特定主题上追加OpenStreetMap信息。 Answer questions, and within minutes your contributions are available everywhere. 主题维护者为它定义元素、问题和语言。

发现更多

MapComplete always offers the next step to learn more about OpenStreetMap.

  • 当嵌入在网站的时候,iframe连接到一个全屏的MapComplete
  • 全屏版本提供OpenStreetMap信息
  • 无需登录即可查看,但编辑需要一个OSM账号。
  • 若您未登录则将被要求登录
  • 每次当您回答一条问题,您都可以向地图添加新的点
  • After a while, actual OSM-tags are shown, later linking to the wiki


Did you notice an issue? Do you have a feature request? Want to help translate? Head over to the source code orissue tracker.

想要查看您的进度?查阅OsmCha上的编辑计数。

", "add": { "disableFilters": "禁用所有过滤器", "hasBeenImported": "这个点已经被导入过了" diff --git a/langs/zh_Hant.json b/langs/zh_Hant.json index da51e536d8..c72673f409 100644 --- a/langs/zh_Hant.json +++ b/langs/zh_Hant.json @@ -52,7 +52,6 @@ }, "general": { "about": "相當容易編輯,而且能為開放街圖新增特定主題", - "aboutMapcomplete": "

關於

使用 MapComplete 你可以藉由單一主題新增開放街圖的圖資。回答幾個問題,然後幾分鐘之內你的貢獻立刻就傳遍全球!大部分的主題都能新增圖片甚至留下評論。主題維護者定議主題的元素、問題與語言。

發現更多

MapComplete 總是提供學習更多開放街圖下一步的知識

  • 當你內嵌網站,網頁內嵌會連結到全螢幕的 MapComplete
  • 全螢幕的版本提供關於開放街圖的資訊
  • 不登入檢視成果,但是要編輯則需要 OSM 帳號。
  • 如果你沒有登入,你會被要求先登入
  • 當你回答單一問題時,你可以在地圖新增新的圖徵
  • 過了一陣子,實際的 OSM-標籤會顯示,之後會連結到 wiki


你有注意到問題嗎?你想請求功能嗎?想要幫忙翻譯嗎?來到原始碼或是問題追蹤器。

想要看到你的進度嗎?到OsmCha追蹤編輯數。

", "add": { "addNew": "在這裡新增新的 {category}", "confirmButton": "在此新增 {category}。
大家都可以看到您新增的內容
", From c6e12fdd6b401b03afbc9adb5728b6f665a64b15 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 20 Apr 2023 18:58:31 +0200 Subject: [PATCH 051/257] Refactoring: fix GPX-track view --- Logic/Actors/GeoLocationHandler.ts | 12 +- Logic/Actors/SelectedElementTagsUpdater.ts | 13 ++ .../Actors/FeaturePropertiesStore.ts | 23 ++- Logic/FeatureSource/FeatureSource.ts | 8 +- .../Sources/FilteringFeatureSource.ts | 6 +- Logic/GeoOperations.ts | 127 ++++++++++------- Logic/MetaTagging.ts | 8 +- Logic/State/MapState.ts | 133 ------------------ Models/ThemeConfig/Conversion/Validation.ts | 2 +- Models/ThemeConfig/LayerConfig.ts | 19 ++- Models/ThemeConfig/LayoutConfig.ts | 3 + Models/ThemeViewState.ts | 26 ++-- UI/BigComponents/UploadTraceToOsmUI.ts | 4 +- UI/Input/InputElementWrapper.ts | 43 ------ UI/Popup/AutoApplyButton.ts | 17 ++- UI/Popup/ExportAsGpxViz.ts | 20 ++- UI/Popup/TagRenderingQuestion.ts | 19 +-- UI/Popup/UploadToOsmViz.ts | 35 +---- UI/SpecialVisualization.ts | 4 +- UI/SpecialVisualizations.ts | 10 +- Utils.ts | 29 ++++ assets/layers/gps_track/gps_track.json | 2 +- package.json | 1 - 23 files changed, 217 insertions(+), 347 deletions(-) delete mode 100644 Logic/State/MapState.ts delete mode 100644 UI/Input/InputElementWrapper.ts diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index b754d6f60d..e5a1b195dd 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -4,7 +4,7 @@ import Constants from "../../Models/Constants" import { GeoLocationPointProperties, GeoLocationState } from "../State/GeoLocationState" import { UIEventSource } from "../UIEventSource" import { Feature, LineString, Point } from "geojson" -import { FeatureSource } from "../FeatureSource/FeatureSource" +import { FeatureSource, WritableFeatureSource } from "../FeatureSource/FeatureSource" import { LocalStorageSource } from "../Web/LocalStorageSource" import { GeoOperations } from "../GeoOperations" import { OsmTags } from "../../Models/OsmFeature" @@ -27,14 +27,14 @@ export default class GeoLocationHandler { /** * All previously visited points (as 'Point'-objects), with their metadata */ - public historicalUserLocations: FeatureSource + public historicalUserLocations: WritableFeatureSource> /** * A featureSource containing a single linestring which has the GPS-history of the user. * However, metadata (such as when every single point was visited) is lost here (but is kept in `historicalUserLocations`. * Note that this featureSource is _derived_ from 'historicalUserLocations' */ - public historicalUserLocationsTrack: FeatureSource + public readonly historicalUserLocationsTrack: FeatureSource public readonly mapHasMoved: UIEventSource = new UIEventSource(false) private readonly selectedElement: UIEventSource private readonly mapProperties?: MapProperties @@ -90,7 +90,7 @@ export default class GeoLocationHandler { geolocationState.allowMoving.syncWith(mapProperties.allowMoving, true) this.CopyGeolocationIntoMapstate() - this.initUserLocationTrail() + this.historicalUserLocationsTrack = this.initUserLocationTrail() } /** @@ -220,7 +220,7 @@ export default class GeoLocationHandler { features.ping() }) - this.historicalUserLocations = new StaticFeatureSource(features) + this.historicalUserLocations = new StaticFeatureSource(features) const asLine = features.map((allPoints) => { if (allPoints === undefined || allPoints.length < 2) { @@ -242,6 +242,6 @@ export default class GeoLocationHandler { } return [feature] }) - this.historicalUserLocationsTrack = new StaticFeatureSource(asLine) + return new StaticFeatureSource(asLine) } } diff --git a/Logic/Actors/SelectedElementTagsUpdater.ts b/Logic/Actors/SelectedElementTagsUpdater.ts index 135fbd6e44..f3a146bb39 100644 --- a/Logic/Actors/SelectedElementTagsUpdater.ts +++ b/Logic/Actors/SelectedElementTagsUpdater.ts @@ -10,6 +10,8 @@ import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesSto import { Feature } from "geojson" import { OsmTags } from "../../Models/OsmFeature" import OsmObjectDownloader from "../Osm/OsmObjectDownloader" +import { IndexedFeatureSource } from "../FeatureSource/FeatureSource" +import { Utils } from "../../Utils" export default class SelectedElementTagsUpdater { private static readonly metatags = new Set([ @@ -28,11 +30,13 @@ export default class SelectedElementTagsUpdater { osmConnection: OsmConnection layout: LayoutConfig osmObjectDownloader: OsmObjectDownloader + indexedFeatures: IndexedFeatureSource } constructor(state: { selectedElement: UIEventSource featureProperties: FeaturePropertiesStore + indexedFeatures: IndexedFeatureSource changes: Changes osmConnection: OsmConnection layout: LayoutConfig @@ -82,7 +86,16 @@ export default class SelectedElementTagsUpdater { return } const latestTags = osmObject.tags + const newGeometry = osmObject.asGeoJson()?.geometry + const oldFeature = state.indexedFeatures.featuresById.data.get(id) + const oldGeometry = oldFeature?.geometry + if (oldGeometry !== undefined && !Utils.SameObject(newGeometry, oldGeometry)) { + console.log("Detected a difference in geometry for ", id) + oldFeature.geometry = newGeometry + state.featureProperties.getStore(id)?.ping() + } this.applyUpdate(latestTags, id) + console.log("Updated", id) } catch (e) { console.warn("Could not update", id, " due to", e) diff --git a/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts b/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts index 9cadb5df31..3d33040a85 100644 --- a/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts +++ b/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts @@ -5,11 +5,19 @@ import { UIEventSource } from "../../UIEventSource" * Constructs a UIEventStore for the properties of every Feature, indexed by id */ export default class FeaturePropertiesStore { - private readonly _source: FeatureSource & IndexedFeatureSource private readonly _elements = new Map>>() - constructor(source: FeatureSource & IndexedFeatureSource) { - this._source = source + constructor(...sources: FeatureSource[]) { + for (const source of sources) { + this.trackFeatureSource(source) + } + } + + public getStore(id: string): UIEventSource> { + return this._elements.get(id) + } + + public trackFeatureSource(source: FeatureSource) { const self = this source.features.addCallbackAndRunD((features) => { console.log("Re-indexing features") @@ -41,14 +49,6 @@ export default class FeaturePropertiesStore { }) } - public getStore(id: string): UIEventSource> { - return this._elements.get(id) - } - - public addSpecial(id: string, store: UIEventSource>) { - this._elements.set(id, store) - } - /** * Overwrites the tags of the old properties object, returns true if a change was made. * Metatags are overriden if they are in the new properties, but not removed @@ -87,7 +87,6 @@ export default class FeaturePropertiesStore { // noinspection JSUnusedGlobalSymbols public addAlias(oldId: string, newId: string): void { - console.log("FeaturePropertiesStore: adding alias for", oldId, newId) if (newId === undefined) { // We removed the node/way/relation with type 'type' and id 'oldId' on openstreetmap! const element = this._elements.get(oldId) diff --git a/Logic/FeatureSource/FeatureSource.ts b/Logic/FeatureSource/FeatureSource.ts index def7d53e5c..afe0b60920 100644 --- a/Logic/FeatureSource/FeatureSource.ts +++ b/Logic/FeatureSource/FeatureSource.ts @@ -3,11 +3,11 @@ import FilteredLayer from "../../Models/FilteredLayer" import { BBox } from "../BBox" import { Feature } from "geojson" -export interface FeatureSource { - features: Store +export interface FeatureSource { + features: Store } -export interface WritableFeatureSource extends FeatureSource { - features: UIEventSource +export interface WritableFeatureSource extends FeatureSource { + features: UIEventSource } export interface Tiled { diff --git a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts index 8d1636bee2..9a55a51d15 100644 --- a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts @@ -61,7 +61,7 @@ export default class FilteringFeatureSource implements FeatureSource { const includedFeatureIds = new Set() const globalFilters = self._globalFilters?.data?.map((f) => f) const newFeatures = (features ?? []).filter((f) => { - self.registerCallback(f) + self.registerCallback(f.properties.id) if (!layer.isShown(f.properties, globalFilters)) { return false @@ -91,11 +91,11 @@ export default class FilteringFeatureSource implements FeatureSource { this.features.setData(newFeatures) } - private registerCallback(feature: any) { + private registerCallback(featureId: string) { if (this._fetchStore === undefined) { return } - const src = this._fetchStore(feature) + const src = this._fetchStore(featureId) if (src == undefined) { return } diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 741affcb9f..a26261194c 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -1,7 +1,6 @@ import { BBox } from "./BBox" -import LayerConfig from "../Models/ThemeConfig/LayerConfig" import * as turf from "@turf/turf" -import { AllGeoJSON, booleanWithin, Coord, Lines } from "@turf/turf" +import { AllGeoJSON, booleanWithin, Coord } from "@turf/turf" import { Feature, FeatureCollection, @@ -14,9 +13,8 @@ import { Polygon, Position, } from "geojson" -import togpx from "togpx" -import Constants from "../Models/Constants" import { Tiles } from "../Models/TileRange" +import { Utils } from "../Utils" export class GeoOperations { private static readonly _earthRadius = 6378137 @@ -416,30 +414,55 @@ export class GeoOperations { .features.map((p) => <[number, number]>p.geometry.coordinates) } - public static AsGpx( - feature: Feature, - options?: { layer?: LayerConfig; gpxMetadata?: any } - ): string { - const metadata = options?.gpxMetadata ?? {} - metadata["time"] = metadata["time"] ?? new Date().toISOString() - const tags = feature.properties - - if (options?.layer !== undefined) { - metadata["name"] = options?.layer.title?.GetRenderValue(tags)?.Subs(tags)?.txt - metadata["desc"] = "Generated with MapComplete layer " + options?.layer.id - if (tags._backend?.contains("openstreetmap")) { - metadata["copyright"] = - "Data copyrighted by OpenStreetMap-contributors, freely available under ODbL. See https://www.openstreetmap.org/copyright" - metadata["author"] = tags["_last_edit:contributor"] - metadata["link"] = "https://www.openstreetmap.org/" + tags.id - metadata["time"] = tags["_last_edit:timestamp"] - } + public static toGpx( + locations: + | Feature + | Feature[], + title?: string + ) { + title = title?.trim() + if (title === undefined || title === "") { + title = "Uploaded with MapComplete" } - - return togpx(feature, { - creator: "MapComplete " + Constants.vNumber, - metadata, - }) + title = Utils.EncodeXmlValue(title) + const trackPoints: string[] = [] + let locationsWithMeta: Feature[] + if (Array.isArray(locations)) { + locationsWithMeta = locations + } else { + locationsWithMeta = locations.geometry.coordinates.map( + (p) => + >{ + type: "Feature", + properties: {}, + geometry: { + type: "Point", + coordinates: p, + }, + } + ) + } + for (const l of locationsWithMeta) { + let trkpt = ` ` + if (l.properties.date) { + trkpt += ` ` + } + if (l.properties.altitude) { + trkpt += ` ${l.properties.altitude}` + } + trkpt += " " + trackPoints.push(trkpt) + } + const header = + '' + return ( + header + + "\n" + + title + + "\n\n" + + trackPoints.join("\n") + + "\n" + ) } public static IdentifieCommonSegments(coordinatess: [number, number][][]): { @@ -807,6 +830,31 @@ export class GeoOperations { return tiles } + /** + * Creates a linestring object based on the outer ring of the given polygon + * + * Returns the argument if not a polygon + * @param p + */ + public static outerRing

(p: Feature): Feature { + if (p.geometry.type !== "Polygon") { + return >p + } + return { + type: "Feature", + properties: p.properties, + geometry: { + type: "LineString", + coordinates: p.geometry.coordinates[0], + }, + } + } + + static centerpointCoordinatesObj(geojson: Feature) { + const [lon, lat] = GeoOperations.centerpointCoordinates(geojson) + return { lon, lat } + } + /** * Helper function which does the heavy lifting for 'inside' */ @@ -956,29 +1004,4 @@ export class GeoOperations { } throw "CalculateIntersection fallthrough: can not calculate an intersection between features" } - - /** - * Creates a linestring object based on the outer ring of the given polygon - * - * Returns the argument if not a polygon - * @param p - */ - public static outerRing

(p: Feature): Feature { - if (p.geometry.type !== "Polygon") { - return >p - } - return { - type: "Feature", - properties: p.properties, - geometry: { - type: "LineString", - coordinates: p.geometry.coordinates[0], - }, - } - } - - static centerpointCoordinatesObj(geojson: Feature) { - const [lon, lat] = GeoOperations.centerpointCoordinates(geojson) - return { lon, lat } - } } diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index ce1d143fa5..6a91b08385 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -1,4 +1,4 @@ -import SimpleMetaTaggers, { SimpleMetaTagger } from "./SimpleMetaTagger" +import SimpleMetaTaggers, { MetataggingState, SimpleMetaTagger } from "./SimpleMetaTagger" import { ExtraFuncParams, ExtraFunctions } from "./ExtraFunctions" import LayerConfig from "../Models/ThemeConfig/LayerConfig" import { Feature } from "geojson" @@ -6,6 +6,7 @@ import FeaturePropertiesStore from "./FeatureSource/Actors/FeaturePropertiesStor import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" import { GeoIndexedStoreForLayer } from "./FeatureSource/Actors/GeoIndexedStore" import { IndexedFeatureSource } from "./FeatureSource/FeatureSource" +import OsmObjectDownloader from "./Osm/OsmObjectDownloader" /** * Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ... @@ -19,6 +20,7 @@ export default class MetaTagging { constructor(state: { layout: LayoutConfig + osmObjectDownloader: OsmObjectDownloader perLayer: ReadonlyMap indexedFeatures: IndexedFeatureSource featureProperties: FeaturePropertiesStore @@ -39,6 +41,7 @@ export default class MetaTagging { params, layer, state.layout, + state.osmObjectDownloader, state.featureProperties ) }) @@ -56,6 +59,7 @@ export default class MetaTagging { params: ExtraFuncParams, layer: LayerConfig, layout: LayoutConfig, + osmObjectDownloader: OsmObjectDownloader, featurePropertiesStores?: FeaturePropertiesStore, options?: { includeDates?: true | boolean @@ -83,7 +87,7 @@ export default class MetaTagging { // The calculated functions - per layer - which add the new keys const layerFuncs = this.createRetaggingFunc(layer) - const state = { layout } + const state: MetataggingState = { layout, osmObjectDownloader } let atLeastOneFeatureChanged = false diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts deleted file mode 100644 index 12295cfb88..0000000000 --- a/Logic/State/MapState.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { Store, UIEventSource } from "../UIEventSource" -import FilteredLayer from "../../Models/FilteredLayer" -import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" -import { QueryParameters } from "../Web/QueryParameters" -import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer" -import { FeatureSource, FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource" -import StaticFeatureSource, { - TiledStaticFeatureSource, -} from "../FeatureSource/Sources/StaticFeatureSource" -import { Feature } from "geojson" -import { MapProperties } from "../../Models/MapProperties" - -/** - * Contains all the leaflet-map related state - */ -export default class MapState { - /** - * Last location where a click was registered - */ - public readonly LastClickLocation: UIEventSource<{ - lat: number - lon: number - }> = new UIEventSource<{ lat: number; lon: number }>(undefined) - - /** - * The bounds of the current map view - */ - public currentView: FeatureSourceForLayer & Tiled - - /** - * A builtin layer which contains the selected element. - * Loads 'selected_element.json' - * This _might_ contain multiple points, e.g. every center of a multipolygon - */ - public selectedElementsLayer: FeatureSourceForLayer & Tiled - - /** - * Which overlays are shown - */ - public overlayToggles: { config: TilesourceConfig; isDisplayed: UIEventSource }[] - - constructor() { - this.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(this.locationControl) - - let defaultLayer = AvailableBaseLayers.osmCarto - const available = this.availableBackgroundLayers.data - for (const layer of available) { - if (this.backgroundLayerId.data === layer.id) { - defaultLayer = layer - } - } - const self = this - this.backgroundLayer = new UIEventSource(defaultLayer) - this.backgroundLayer.addCallbackAndRunD((layer) => self.backgroundLayerId.setData(layer.id)) - - this.overlayToggles = - this.layoutToUse?.tileLayerSources - ?.filter((c) => c.name !== undefined) - ?.map((c) => ({ - config: c, - isDisplayed: QueryParameters.GetBooleanQueryParameter( - "overlay-" + c.id, - c.defaultState, - "Wether or not the overlay " + c.id + " is shown" - ), - })) ?? [] - - this.AddAllOverlaysToMap(this.leafletMap) - - this.initCurrentView() - this.initSelectedElement() - } - - public AddAllOverlaysToMap(leafletMap: UIEventSource) { - const initialized = new Set() - for (const overlayToggle of this.overlayToggles) { - new ShowOverlayLayer(overlayToggle.config, leafletMap, overlayToggle.isDisplayed) - initialized.add(overlayToggle.config) - } - - for (const tileLayerSource of this.layoutToUse?.tileLayerSources ?? []) { - if (initialized.has(tileLayerSource)) { - continue - } - new ShowOverlayLayer(tileLayerSource, leafletMap) - } - } - - private static initCurrentView(mapproperties: MapProperties): FeatureSource { - let i = 0 - const features: Store = mapproperties.bounds.map((bounds) => { - if (bounds === undefined) { - return [] - } - i++ - return [ - bounds.asGeoJson({ - id: "current_view-" + i, - current_view: "yes", - zoom: "" + mapproperties.zoom.data, - }), - ] - }) - - return new StaticFeatureSource(features) - } - - private initSelectedElement() { - const layerDef: FilteredLayer = this.filteredLayers.data.filter( - (l) => l.layerDef.id === "selected_element" - )[0] - const empty = [] - const store = this.selectedElement.map((feature) => { - if (feature === undefined || feature === null) { - return empty - } - return [ - { - feature: { - type: "Feature", - properties: { - selected: "yes", - id: "selected" + feature.properties.id, - }, - geometry: feature.geometry, - }, - freshness: new Date(), - }, - ] - }) - this.selectedElementsLayer = new TiledStaticFeatureSource(store, layerDef) - } -} diff --git a/Models/ThemeConfig/Conversion/Validation.ts b/Models/ThemeConfig/Conversion/Validation.ts index da89260a82..1a14d735dd 100644 --- a/Models/ThemeConfig/Conversion/Validation.ts +++ b/Models/ThemeConfig/Conversion/Validation.ts @@ -891,7 +891,7 @@ export class ValidateLayer extends DesugaringStep { throw "A special layer cannot have presets" } // Check that a preset will be picked up by the layer itself - const baseTags = TagUtils.Tag(json.source.osmTags) + const baseTags = TagUtils.Tag(json.source["osmTags"]) for (let i = 0; i < json.presets.length; i++) { const preset = json.presets[i] const tags: { k: string; v: string }[] = new And( diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index 13212878c7..b81bf244d6 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -618,17 +618,26 @@ export default class LayerConfig extends WithContextLoader { filterDocs.push(new Title("Filters", 4)) filterDocs.push(...this.filters.map((filter) => filter.GenerateDocs())) } + + const tagsDescription = [] + if (this.source === null) { + tagsDescription.push( + new Title("Basic tags for this layer", 2), + "Elements must have the all of following tags to be shown on this layer:", + new List(neededTags.map((t) => t.asHumanString(true, false, {}))), + overpassLink + ) + } else { + tagsDescription.push("This is a special layer - data is not sourced from OpenStreetMap") + } + return new Combine([ new Combine([new Title(this.id, 1), iconImg, this.description, "\n"]).SetClass( "flex flex-col" ), new List(extraProps), ...usingLayer, - - new Title("Basic tags for this layer", 2), - "Elements must have the all of following tags to be shown on this layer:", - new List(neededTags.map((t) => t.asHumanString(true, false, {}))), - overpassLink, + ...tagsDescription, new Title("Supported attributes", 2), quickOverview, ...this.tagRenderings.map((tr) => tr.GenerateDocumentation()), diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts index a852401202..5a7e6bb69f 100644 --- a/Models/ThemeConfig/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -291,6 +291,9 @@ export default class LayoutConfig implements LayoutInformation { return undefined } for (const layer of this.layers) { + if (!layer.source) { + continue + } if (layer.source.osmTags.matchesProperties(tags)) { return layer } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 3ac0d7a2c0..8154ff9705 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -10,7 +10,7 @@ import { import { OsmConnection } from "../Logic/Osm/OsmConnection" import { ExportableMap, MapProperties } from "./MapProperties" import LayerState from "../Logic/State/LayerState" -import { Feature } from "geojson" +import { Feature, Point } from "geojson" import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" import { Map as MlMap } from "maplibre-gl" import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning" @@ -42,7 +42,7 @@ import { MenuState } from "./MenuState" import MetaTagging from "../Logic/MetaTagging" import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator" import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource" -import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; +import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" /** * @@ -71,8 +71,8 @@ export default class ThemeViewState implements SpecialVisualizationState { readonly guistate: MenuState readonly fullNodeDatabase?: FullNodeDatabaseSource // TODO - readonly historicalUserLocations: WritableFeatureSource - readonly indexedFeatures: IndexedFeatureSource + readonly historicalUserLocations: WritableFeatureSource> + readonly indexedFeatures: IndexedFeatureSource & LayoutSource readonly newFeatures: WritableFeatureSource readonly layerState: LayerState readonly perLayer: ReadonlyMap @@ -152,6 +152,7 @@ export default class ThemeViewState implements SpecialVisualizationState { }, layout?.isLeftRightSensitive() ?? false ) + this.historicalUserLocations = this.geolocation.historicalUserLocations this.newFeatures = new NewGeometryFromChangesFeatureSource( this.changes, indexedElements, @@ -215,7 +216,10 @@ export default class ThemeViewState implements SpecialVisualizationState { this.layout )) - this.osmObjectDownloader = new OsmObjectDownloader(this.osmConnection.Backend(), this.changes) + this.osmObjectDownloader = new OsmObjectDownloader( + this.osmConnection.Backend(), + this.changes + ) this.initActors() this.drawSpecialLayers(lastClick) @@ -274,7 +278,6 @@ export default class ThemeViewState implements SpecialVisualizationState { /** * Add the special layers to the map - * @private */ private drawSpecialLayers(last_click: LastClickFeatureSource) { type AddedByDefaultTypes = typeof Constants.added_by_default[number] @@ -283,10 +286,8 @@ export default class ThemeViewState implements SpecialVisualizationState { // The last_click gets a _very_ special treatment const last_click_layer = this.layerState.filteredLayers.get("last_click") - this.featureProperties.addSpecial( - "last_click", - new UIEventSource>(last_click.properties) - ) + this.featureProperties.trackFeatureSource(last_click) + this.indexedFeatures.addSource(last_click) new ShowDataLayer(this.map, { features: new FilteringFeatureSource(last_click_layer, last_click), doShowLayer: new ImmutableStore(true), @@ -347,10 +348,13 @@ export default class ThemeViewState implements SpecialVisualizationState { ?.isDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true) this.layerState.filteredLayers.forEach((flayer) => { - const features = specialLayers[flayer.layerDef.id] + const features: FeatureSource = specialLayers[flayer.layerDef.id] if (features === undefined) { return } + + this.featureProperties.trackFeatureSource(features) + this.indexedFeatures.addSource(features) new ShowDataLayer(this.map, { features, doShowLayer: flayer.isDisplayed, diff --git a/UI/BigComponents/UploadTraceToOsmUI.ts b/UI/BigComponents/UploadTraceToOsmUI.ts index 3d7c43910a..be0ca46bad 100644 --- a/UI/BigComponents/UploadTraceToOsmUI.ts +++ b/UI/BigComponents/UploadTraceToOsmUI.ts @@ -92,13 +92,13 @@ export default class UploadTraceToOsmUI extends LoginToggle { ) const descriptionStr = UploadTraceToOsmUI.createDefault( description.GetValue().data, - "Track created with MapComplete with theme " + state?.layoutToUse?.id + "Track created with MapComplete with theme " + state?.layout?.id ) await state?.osmConnection?.uploadGpxTrack(trace(title.GetValue().data), { visibility: dropdown.GetValue().data, description: descriptionStr, filename: titleStr + ".gpx", - labels: ["MapComplete", state?.layoutToUse?.id], + labels: ["MapComplete", state?.layout?.id], }) if (options?.whenUploaded !== undefined) { diff --git a/UI/Input/InputElementWrapper.ts b/UI/Input/InputElementWrapper.ts deleted file mode 100644 index 99fa3309ea..0000000000 --- a/UI/Input/InputElementWrapper.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { InputElement } from "./InputElement" -import { UIEventSource } from "../../Logic/UIEventSource" -import BaseUIElement from "../BaseUIElement" -import { Translation } from "../i18n/Translation" -import { SubstitutedTranslation } from "../SubstitutedTranslation" -import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" - -export default class InputElementWrapper extends InputElement { - private readonly _inputElement: InputElement - private readonly _renderElement: BaseUIElement - - constructor( - inputElement: InputElement, - translation: Translation, - key: string, - tags: UIEventSource, - state: FeaturePipelineState - ) { - super() - this._inputElement = inputElement - const mapping = new Map() - - mapping.set(key, inputElement) - - // Bit of a hack: the SubstitutedTranslation expects a special rendering, but those are formatted '{key()}' instead of '{key}', so we substitute it first - translation = translation.OnEveryLanguage((txt) => - txt.replace("{" + key + "}", "{" + key + "()}") - ) - this._renderElement = new SubstitutedTranslation(translation, tags, state, mapping) - } - - GetValue(): UIEventSource { - return this._inputElement.GetValue() - } - - IsValid(t: T): boolean { - return this._inputElement.IsValid(t) - } - - protected InnerConstructElement(): HTMLElement { - return this._renderElement.ConstructElement() - } -} diff --git a/UI/Popup/AutoApplyButton.ts b/UI/Popup/AutoApplyButton.ts index 47d1b633f1..e30fe6a044 100644 --- a/UI/Popup/AutoApplyButton.ts +++ b/UI/Popup/AutoApplyButton.ts @@ -5,7 +5,6 @@ import Img from "../Base/Img" import { FixedUiElement } from "../Base/FixedUiElement" import Combine from "../Base/Combine" import Link from "../Base/Link" -import { SubstitutedTranslation } from "../SubstitutedTranslation" import { Utils } from "../../Utils" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import { VariableUiElement } from "../Base/VariableUIElement" @@ -25,6 +24,7 @@ import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" import ShowDataLayer from "../Map/ShowDataLayer" import SvelteUIElement from "../Base/SvelteUIElement" import MaplibreMap from "../Map/MaplibreMap.svelte" +import SpecialVisualizations from "../SpecialVisualizations" export interface AutoAction extends SpecialVisualization { supportsAutoAction: boolean @@ -148,19 +148,22 @@ class ApplyButton extends UIElement { const featureTags = this.state.featureProperties.getStore(targetFeatureId) const rendering = this.tagRenderingConfig.GetRenderValue(featureTags.data).txt const specialRenderings = Utils.NoNull( - SubstitutedTranslation.ExtractSpecialComponents(rendering).map((x) => x.special) - ).filter((v) => v.func["supportsAutoAction"] === true) + SpecialVisualizations.constructSpecification(rendering) + ).filter((v) => typeof v !== "string" && v.func["supportsAutoAction"] === true) if (specialRenderings.length == 0) { console.warn( "AutoApply: feature " + - targetFeatureId + - " got a rendering without supported auto actions:", + targetFeatureId + + " got a rendering without supported auto actions:", rendering ) } for (const specialRendering of specialRenderings) { + if (typeof specialRendering === "string") { + continue + } const action = specialRendering.func await action.applyActionOn(this.state, featureTags, specialRendering.args) } @@ -225,7 +228,7 @@ export default class AutoApplyButton implements SpecialVisualization { "To effectively use this button, you'll need some ingredients:", new List([ "A target layer with features for which an action is defined in a tag rendering. The following special visualisations support an autoAction: " + - supportedActions.join(", "), + supportedActions.join(", "), "A host feature to place the auto-action on. This can be a big outline (such as a city). Another good option for this is the layer ", new Link("current_view", "./BuiltinLayers.md#current_view"), "Then, use a calculated tag on the host feature to determine the overlapping object ids", @@ -245,7 +248,7 @@ export default class AutoApplyButton implements SpecialVisualization { !( state.featureSwitchIsTesting.data || state.osmConnection._oauth_config.url === - OsmConnection.oauth_configs["osm-test"].url + OsmConnection.oauth_configs["osm-test"].url ) ) { const t = Translations.t.general.add.import diff --git a/UI/Popup/ExportAsGpxViz.ts b/UI/Popup/ExportAsGpxViz.ts index eb5241c510..314f9ac21c 100644 --- a/UI/Popup/ExportAsGpxViz.ts +++ b/UI/Popup/ExportAsGpxViz.ts @@ -5,16 +5,26 @@ import Combine from "../Base/Combine" import { GeoOperations } from "../../Logic/GeoOperations" import { Utils } from "../../Utils" import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" -import { UIEventSource } from "../../Logic/UIEventSource" +import { Store, UIEventSource } from "../../Logic/UIEventSource" +import { Feature, LineString } from "geojson" +import LayerConfig from "../../Models/ThemeConfig/LayerConfig" export class ExportAsGpxViz implements SpecialVisualization { funcName = "export_as_gpx" docs = "Exports the selected feature as GPX-file" args = [] - constr(state: SpecialVisualizationState, tagSource: UIEventSource>) { + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ) { const t = Translations.t.general.download - + if (feature.geometry.type !== "LineString") { + return undefined + } return new SubtleButton( Svg.download_ui(), new Combine([ @@ -24,10 +34,8 @@ export class ExportAsGpxViz implements SpecialVisualization { ).onClick(() => { console.log("Exporting as GPX!") const tags = tagSource.data - const feature = state.indexedFeatures.featuresById.data.get(tags.id) - const layer = state?.layout?.getMatchingLayer(tags) - const gpx = GeoOperations.AsGpx(feature, { layer }) const title = layer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track" + const gpx = GeoOperations.toGpx(>feature, title) Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", { mimetype: "{gpx=application/gpx+xml}", }) diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index 296c451b66..1530a1b404 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -9,9 +9,7 @@ import InputElementMap from "../Input/InputElementMap" import { SaveButton } from "./SaveButton" import { VariableUiElement } from "../Base/VariableUIElement" import Translations from "../i18n/Translations" -import { FixedUiElement } from "../Base/FixedUiElement" import { Translation } from "../i18n/Translation" -import Constants from "../../Models/Constants" import { SubstitutedTranslation } from "../SubstitutedTranslation" import { TagsFilter } from "../../Logic/Tags/TagsFilter" import { Tag } from "../../Logic/Tags/Tag" @@ -19,7 +17,6 @@ import { And } from "../../Logic/Tags/And" import { TagUtils, UploadableTag } from "../../Logic/Tags/TagUtils" import BaseUIElement from "../BaseUIElement" import { DropDown } from "../Input/DropDown" -import InputElementWrapper from "../Input/InputElementWrapper" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import TagRenderingConfig, { Mapping } from "../../Models/ThemeConfig/TagRenderingConfig" import { Unit } from "../../Models/Unit" @@ -626,25 +623,11 @@ export default class TagRenderingQuestion extends Combine { } }) - let inputTagsFilter: InputElement = new InputElementMap( + return new InputElementMap( input, (a, b) => a === b || (a?.shadows(b) ?? false), pickString, toString ) - - if (freeform.inline) { - inputTagsFilter.SetClass("w-48-imp") - inputTagsFilter = new InputElementWrapper( - inputTagsFilter, - configuration.render, - freeform.key, - tags, - state - ) - inputTagsFilter.SetClass("block") - } - - return inputTagsFilter } } diff --git a/UI/Popup/UploadToOsmViz.ts b/UI/Popup/UploadToOsmViz.ts index f25f723867..27f102008a 100644 --- a/UI/Popup/UploadToOsmViz.ts +++ b/UI/Popup/UploadToOsmViz.ts @@ -5,6 +5,7 @@ import { GeoLocationPointProperties } from "../../Logic/State/GeoLocationState" import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI" import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import { UIEventSource } from "../../Logic/UIEventSource" +import { GeoOperations } from "../../Logic/GeoOperations" /** * Wrapper around 'UploadTraceToOsmUI' @@ -20,38 +21,8 @@ export class UploadToOsmViz implements SpecialVisualization { featureTags: UIEventSource>, args: string[] ) { - function getTrace(title: string) { - title = title?.trim() - if (title === undefined || title === "") { - title = "Uploaded with MapComplete" - } - title = Utils.EncodeXmlValue(title) - const userLocations = []>( - state.historicalUserLocations.features.data - ) - const trackPoints: string[] = [] - for (const l of userLocations) { - let trkpt = ` ` - trkpt += ` ` - if (l.properties.altitude !== null && l.properties.altitude !== undefined) { - trkpt += ` ${l.properties.altitude}` - } - trkpt += " " - trackPoints.push(trkpt) - } - const header = - '' - return ( - header + - "\n" + - title + - "\n\n" + - trackPoints.join("\n") + - "\n" - ) - } - - return new UploadTraceToOsmUI(getTrace, state, { + const locations = state.historicalUserLocations.features.data + return new UploadTraceToOsmUI((title) => GeoOperations.toGpx(locations, title), state, { whenUploaded: async () => { state.historicalUserLocations.features.setData([]) }, diff --git a/UI/SpecialVisualization.ts b/UI/SpecialVisualization.ts index 9e64d16ace..5968f4b4e0 100644 --- a/UI/SpecialVisualization.ts +++ b/UI/SpecialVisualization.ts @@ -6,7 +6,7 @@ import { OsmConnection } from "../Logic/Osm/OsmConnection"; import { Changes } from "../Logic/Osm/Changes"; import { ExportableMap, MapProperties } from "../Models/MapProperties"; import LayerState from "../Logic/State/LayerState"; -import { Feature, Geometry } from "geojson"; +import { Feature, Geometry, Point } from "geojson"; import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; import { MangroveIdentity } from "../Logic/Web/MangroveReviews"; import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"; @@ -34,7 +34,7 @@ export interface SpecialVisualizationState { */ readonly newFeatures: WritableFeatureSource - readonly historicalUserLocations: WritableFeatureSource + readonly historicalUserLocations: WritableFeatureSource> readonly osmConnection: OsmConnection readonly featureSwitchUserbadge: Store diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 3f5d119d62..b556136c37 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -13,7 +13,6 @@ import { MinimapViz } from "./Popup/MinimapViz" import { ShareLinkViz } from "./Popup/ShareLinkViz" import { UploadToOsmViz } from "./Popup/UploadToOsmViz" import { MultiApplyViz } from "./Popup/MultiApplyViz" -import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz" import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz" import { ConflateButton, ImportPointButton, ImportWayButton } from "./Popup/ImportButton" @@ -80,6 +79,7 @@ import DeleteWizard from "./Popup/DeleteWizard" import { OsmId, OsmTags, WayId } from "../Models/OsmFeature" import MoveWizard from "./Popup/MoveWizard" import SplitRoadWizard from "./Popup/SplitRoadWizard" +import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" class NearbyImageVis implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -597,9 +597,9 @@ export default class SpecialVisualizations { }, }, new ShareLinkViz(), + new ExportAsGpxViz(), new UploadToOsmViz(), new MultiApplyViz(), - new ExportAsGpxViz(), new AddNoteCommentViz(), { funcName: "open_note", @@ -874,7 +874,7 @@ export default class SpecialVisualizations { funcName: "export_as_geojson", docs: "Exports the selected feature as GeoJson-file", args: [], - constr: (state, tagSource) => { + constr: (state, tagSource, tagsSource, feature, layer) => { const t = Translations.t.general.download return new SubtleButton( @@ -886,10 +886,8 @@ export default class SpecialVisualizations { ).onClick(() => { console.log("Exporting as Geojson") const tags = tagSource.data - const feature = state.indexedFeatures.featuresById.data.get(tags.id) - const matchingLayer = state?.layout?.getMatchingLayer(tags) const title = - matchingLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson" + layer?.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson" const data = JSON.stringify(feature, null, " ") Utils.offerContentsAsDownloadableFile( data, diff --git a/Utils.ts b/Utils.ts index 20246c84d3..aef5d8d924 100644 --- a/Utils.ts +++ b/Utils.ts @@ -1415,4 +1415,33 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be ) { return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) + Math.abs(c0.b - c1.b) } + + static SameObject(a: any, b: any) { + if (a === b) { + return true + } + if (a === undefined || a === null || b === null || b === undefined) { + return false + } + if (typeof a === "object" && typeof b === "object") { + for (const aKey in a) { + if (!(aKey in b)) { + return false + } + } + + for (const bKey in b) { + if (!(bKey in a)) { + return false + } + } + for (const k in a) { + if (!Utils.SameObject(a[k], b[k])) { + return false + } + } + return true + } + return false + } } diff --git a/assets/layers/gps_track/gps_track.json b/assets/layers/gps_track/gps_track.json index eb0110171c..94bb2a40b8 100644 --- a/assets/layers/gps_track/gps_track.json +++ b/assets/layers/gps_track/gps_track.json @@ -44,4 +44,4 @@ } ], "syncSelection": "global" -} \ No newline at end of file +} diff --git a/package.json b/package.json index 319dc4aea6..e96301bfc4 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,6 @@ "showdown": "^2.1.0", "svg-path-parser": "^1.1.0", "tailwindcss": "^3.1.8", - "togpx": "^0.5.4", "vite-node": "^0.28.3", "vitest": "^0.28.3", "wikibase-sdk": "^7.14.0", From 3aeedf22c81c4fcf11d08ad8278944e21e076344 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 21 Apr 2023 00:25:56 +0200 Subject: [PATCH 052/257] Refactoring: fix rendering artefact when switching selected element --- .../Actors/FeaturePropertiesStore.ts | 1 - Logic/MetaTagging.ts | 2 +- Models/ThemeViewState.ts | 4 +++ UI/ThemeViewGUI.svelte | 34 ++++++++++++------- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts b/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts index 3d33040a85..d7e91d8fdc 100644 --- a/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts +++ b/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts @@ -20,7 +20,6 @@ export default class FeaturePropertiesStore { public trackFeatureSource(source: FeatureSource) { const self = this source.features.addCallbackAndRunD((features) => { - console.log("Re-indexing features") for (const feature of features) { const id = feature.properties.id if (id === undefined) { diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 6a91b08385..b8ff65993a 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -71,7 +71,7 @@ export default class MetaTagging { return } - console.log("Recalculating metatags...") + console.trace("Recalculating metatags...") const metatagsToApply: SimpleMetaTagger[] = [] for (const metatag of SimpleMetaTaggers.metatags) { if (metatag.includesDates) { diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 8154ff9705..3719f61ad1 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -302,6 +302,10 @@ export default class ThemeViewState implements SpecialVisualizationState { }) return } + // We first clear the selection to make sure no weird state is around + this.selectedLayer.setData(undefined) + this.selectedElement.setData(undefined) + this.selectedElement.setData(feature) this.selectedLayer.setData(last_click_layer.layerDef) }, diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 1152f3bf07..670af6561d 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -1,5 +1,5 @@ +{#if layerproperties.name} +

+ +
+{/if} diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index a0345a7355..acc9ac1d6a 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -1,6 +1,6 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" import type { Map as MLMap } from "maplibre-gl" -import { Map as MlMap } from "maplibre-gl" +import { Map as MlMap, SourceSpecification } from "maplibre-gl" import { RasterLayerPolygon, RasterLayerProperties } from "../../Models/RasterLayers" import { Utils } from "../../Utils" import { BBox } from "../../Logic/BBox" @@ -37,6 +37,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { readonly allowZooming: UIEventSource readonly lastClickLocation: Store readonly minzoom: UIEventSource + readonly maxzoom: UIEventSource private readonly _maplibreMap: Store /** * Used for internal bookkeeping (to remove a rasterLayer when done loading) @@ -50,12 +51,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { this.location = state?.location ?? new UIEventSource({ lon: 0, lat: 0 }) this.zoom = state?.zoom ?? new UIEventSource(1) this.minzoom = state?.minzoom ?? new UIEventSource(0) + this.maxzoom = state?.maxzoom ?? new UIEventSource(24) this.zoom.addCallbackAndRunD((z) => { if (z < this.minzoom.data) { this.zoom.setData(this.minzoom.data) } - if (z > 24) { - this.zoom.setData(24) + const max = Math.min(24, this.maxzoom.data ?? 24) + if (z > max) { + this.zoom.setData(max) } }) this.maxbounds = state?.maxbounds ?? new UIEventSource(undefined) @@ -90,6 +93,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { self.setAllowMoving(self.allowMoving.data) self.setAllowZooming(self.allowZooming.data) self.setMinzoom(self.minzoom.data) + self.setMaxzoom(self.maxzoom.data) self.setBounds(self.bounds.data) }) self.MoveMapToCurrentLoc(self.location.data) @@ -98,6 +102,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { self.setAllowMoving(self.allowMoving.data) self.setAllowZooming(self.allowZooming.data) self.setMinzoom(self.minzoom.data) + self.setMaxzoom(self.maxzoom.data) self.setBounds(self.bounds.data) this.updateStores() map.on("moveend", () => this.updateStores()) @@ -146,10 +151,23 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { } } + public static prepareWmsSource(layer: RasterLayerProperties): SourceSpecification { + return { + type: "raster", + // use the tiles option to specify a 256WMS tile source URL + // https://maplibre.org/maplibre-gl-js-docs/style-spec/sources/ + tiles: [MapLibreAdaptor.prepareWmsURL(layer.url, layer["tile-size"] ?? 256)], + tileSize: layer["tile-size"] ?? 256, + minzoom: layer["min_zoom"] ?? 1, + maxzoom: layer["max_zoom"] ?? 25, + // scheme: background["type"] === "tms" ? "tms" : "xyz", + } + } + /** * Prepares an ELI-URL to be compatible with mapbox */ - private static prepareWmsURL(url: string, size: number = 256) { + private static prepareWmsURL(url: string, size: number = 256): string { // ELI: LAYERS=OGWRGB13_15VL&STYLES=&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap // PROD: SERVICE=WMS&REQUEST=GetMap&LAYERS=OGWRGB13_15VL&STYLES=&FORMAT=image/jpeg&TRANSPARENT=false&VERSION=1.3.0&WIDTH=256&HEIGHT=256&CRS=EPSG:3857&BBOX=488585.4847988467,6590094.830634755,489196.9810251281,6590706.32686104 @@ -342,16 +360,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { return } - map.addSource(background.id, { - type: "raster", - // use the tiles option to specify a 256WMS tile source URL - // https://maplibre.org/maplibre-gl-js-docs/style-spec/sources/ - tiles: [MapLibreAdaptor.prepareWmsURL(background.url, background["tile-size"] ?? 256)], - tileSize: background["tile-size"] ?? 256, - minzoom: background["min_zoom"] ?? 1, - maxzoom: background["max_zoom"] ?? 25, - // scheme: background["type"] === "tms" ? "tms" : "xyz", - }) + map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background)) map.addLayer( { @@ -405,6 +414,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { map.setMinZoom(minzoom) } + private setMaxzoom(maxzoom: number) { + const map = this._maplibreMap.data + if (map === undefined) { + return + } + map.setMaxZoom(maxzoom) + } + private setAllowZooming(allow: true | boolean | undefined) { const map = this._maplibreMap.data if (map === undefined) { diff --git a/UI/Map/ShowOverlayRasterLayer.ts b/UI/Map/ShowOverlayRasterLayer.ts new file mode 100644 index 0000000000..5661a7bd25 --- /dev/null +++ b/UI/Map/ShowOverlayRasterLayer.ts @@ -0,0 +1,92 @@ +import { RasterLayerProperties } from "../../Models/RasterLayers" +import { Store, UIEventSource } from "../../Logic/UIEventSource" +import { Map as MlMap } from "maplibre-gl" +import { Utils } from "../../Utils" +import { MapLibreAdaptor } from "./MapLibreAdaptor" + +export default class ShowOverlayRasterLayer { + private readonly _map: UIEventSource + private readonly _layer: RasterLayerProperties + private readonly _mapProperties?: { zoom: Store } + private _mllayer + private readonly _isDisplayed?: Store + + constructor( + layer: RasterLayerProperties, + map: UIEventSource, + mapProperties?: { zoom: Store }, + options?: { + isDisplayed?: Store + } + ) { + this._mapProperties = mapProperties + this._layer = layer + this._map = map + this._isDisplayed = options?.isDisplayed + const self = this + map.addCallbackAndRunD((map) => { + self.addLayer() + map.on("load", () => { + self.addLayer() + }) + }) + this.addLayer() + + options?.isDisplayed?.addCallbackAndRun(() => { + self.setVisibility() + }) + + mapProperties?.zoom?.addCallbackAndRun(() => { + self.setVisibility() + }) + } + + private setVisibility() { + let zoom = this._mapProperties?.zoom?.data + let withinRange = zoom === undefined || zoom > this._layer.min_zoom + let isDisplayed = (this._isDisplayed?.data ?? true) && withinRange + this._map.data?.setLayoutProperty( + this._layer.id, + "visibility", + isDisplayed ? "visible" : "none" + ) + } + + private async awaitStyleIsLoaded(): Promise { + const map = this._map.data + if (map === undefined) { + return + } + while (!map?.isStyleLoaded()) { + await Utils.waitFor(250) + } + } + + private async addLayer() { + const map = this._map.data + console.log("Attempting to add ", this._layer.id) + if (map === undefined) { + return + } + await this.awaitStyleIsLoaded() + if (this._mllayer) { + // Already initialized + return + } + const background: RasterLayerProperties = this._layer + + map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background)) + this._mllayer = map.addLayer({ + id: background.id, + type: "raster", + source: background.id, + paint: {}, + }) + map.setLayoutProperty( + this._layer.id, + "visibility", + this._isDisplayed?.data ?? true ? "visible" : "none" + ) + this.setVisibility() + } +} diff --git a/UI/Popup/MinimapViz.ts b/UI/Popup/MinimapViz.ts index 7c6d67fda8..4fd344c67d 100644 --- a/UI/Popup/MinimapViz.ts +++ b/UI/Popup/MinimapViz.ts @@ -75,6 +75,7 @@ export class MinimapViz implements SpecialVisualization { const mlmap = new UIEventSource(undefined) const mla = new MapLibreAdaptor(mlmap) + mla.maxzoom.setData(17) let zoom = 18 if (args[0]) { const parsed = Number(args[0]) diff --git a/UI/ShowDataLayer/ShowOverlayLayerImplementation.ts b/UI/ShowDataLayer/ShowOverlayLayerImplementation.ts deleted file mode 100644 index 64a6d3ab1d..0000000000 --- a/UI/ShowDataLayer/ShowOverlayLayerImplementation.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as L from "leaflet" -import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" -import { UIEventSource } from "../../Logic/UIEventSource" -import ShowOverlayLayer from "./ShowOverlayLayer" - -// TODO port this to maplibre! -export default class ShowOverlayLayerImplementation { - public static Implement() { - ShowOverlayLayer.implementation = ShowOverlayLayerImplementation.AddToMap - } - - public static AddToMap( - config: TilesourceConfig, - leafletMap: UIEventSource, - isShown: UIEventSource = undefined - ) { - leafletMap.map((leaflet) => { - if (leaflet === undefined) { - return - } - - const tileLayer = L.tileLayer(config.source, { - attribution: "", - maxZoom: config.maxzoom, - minZoom: config.minzoom, - // @ts-ignore - wmts: false, - }) - - if (isShown === undefined) { - tileLayer.addTo(leaflet) - } - - isShown?.addCallbackAndRunD((isShown) => { - if (isShown) { - tileLayer.addTo(leaflet) - } else { - leaflet.removeLayer(tileLayer) - } - }) - }) - } -} diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 670af6561d..461efe048a 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -34,7 +34,7 @@ import Hotkeys from "./Base/Hotkeys"; import { VariableUiElement } from "./Base/VariableUIElement"; import SvelteUIElement from "./Base/SvelteUIElement"; - import { onDestroy } from "svelte"; + import OverlayToggle from "./BigComponents/OverlayToggle.svelte"; export let state: ThemeViewState; let layout = state.layout; @@ -51,9 +51,9 @@ if (selectedElement === undefined || layer === undefined) { return undefined; } - + const tags = state.featureProperties.getStore(selectedElement.properties.id); - return new SvelteUIElement(SelectedElementView, {state, layer, selectedElement, tags}) + return new SvelteUIElement(SelectedElementView, { state, layer, selectedElement, tags }); }, [selectedLayer]); @@ -160,6 +160,14 @@ {/each} + {#each layout.tileLayerSources as tilesource} + + {/each} diff --git a/assets/themes/uk_addresses/uk_addresses.json b/assets/themes/uk_addresses/uk_addresses.json index 493e614a5a..b39453b08e 100644 --- a/assets/themes/uk_addresses/uk_addresses.json +++ b/assets/themes/uk_addresses/uk_addresses.json @@ -30,10 +30,10 @@ "tileLayerSources": [ { "id": "property-boundaries", - "source": "https://tiles.osmuk.org/PropertyBoundaries/{z}/{x}/{y}.png", + "url": "https://tiles.osmuk.org/PropertyBoundaries/{z}/{x}/{y}.png", "isOverlay": true, - "minZoom": 18, - "maxZoom": 20, + "min_zoom": 18, + "max_zoom": 20, "defaultState": false, "name": { "en": "Property boundaries by osmuk.org", @@ -695,4 +695,4 @@ "enableShareScreen": false, "enableMoreQuests": false, "credits": "Pieter Vander Vennet, Rob Nickerson, Russ Garrett" -} \ No newline at end of file +} From d8e14927c809e507197f7331bc71b99a1768ff8f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 21 Apr 2023 16:02:36 +0200 Subject: [PATCH 054/257] Refactoring: port wikipedia panel to Svelte --- Logic/Web/Wikidata.ts | 33 +- Logic/Web/Wikipedia.ts | 160 ++++++--- Models/ThemeViewState.ts | 16 +- UI/Base/ScrollableFullScreen.ts | 163 --------- UI/Base/TabbedComponent.ts | 57 ---- UI/Base/TabbedGroup.svelte | 12 + UI/BigComponents/AllDownloads.ts | 33 +- UI/BigComponents/LeftControls.ts | 4 - UI/BigComponents/PlantNetSpeciesSearch.ts | 12 +- UI/DefaultGUI.ts | 15 - UI/Image/ImageUploadFlow.ts | 2 - UI/ImportFlow/MapPreview.ts | 20 -- .../Validators/WikidataValidator.ts | 58 ---- UI/Popup/FeatureInfoBox.ts | 49 --- UI/SpecialVisualizations.ts | 30 +- UI/ThemeViewGUI.svelte | 14 - UI/Wikipedia/WikidataSearchBox.ts | 31 +- UI/Wikipedia/WikipediaArticle.svelte | 46 +++ UI/Wikipedia/WikipediaBox.ts | 321 ------------------ UI/Wikipedia/WikipediaBoxOptions.ts | 7 + UI/Wikipedia/WikipediaPanel.svelte | 56 +++ UI/Wikipedia/WikipediaTitle.svelte | 13 + assets/layers/gps_track/gps_track.json | 2 +- assets/tagRenderings/questions.json | 2 +- assets/themes/uk_addresses/uk_addresses.json | 2 +- langs/en.json | 1 + langs/shared-questions/ca.json | 7 + langs/shared-questions/de.json | 7 + langs/shared-questions/en.json | 7 + langs/shared-questions/fr.json | 7 + langs/shared-questions/nl.json | 7 + test.ts | 15 +- 32 files changed, 362 insertions(+), 847 deletions(-) delete mode 100644 UI/Base/ScrollableFullScreen.ts delete mode 100644 UI/Base/TabbedComponent.ts delete mode 100644 UI/Popup/FeatureInfoBox.ts create mode 100644 UI/Wikipedia/WikipediaArticle.svelte delete mode 100644 UI/Wikipedia/WikipediaBox.ts create mode 100644 UI/Wikipedia/WikipediaBoxOptions.ts create mode 100644 UI/Wikipedia/WikipediaPanel.svelte create mode 100644 UI/Wikipedia/WikipediaTitle.svelte diff --git a/Logic/Web/Wikidata.ts b/Logic/Web/Wikidata.ts index 8818be52fa..aed400358c 100644 --- a/Logic/Web/Wikidata.ts +++ b/Logic/Web/Wikidata.ts @@ -1,5 +1,5 @@ import { Utils } from "../../Utils" -import { UIEventSource } from "../UIEventSource" +import { Store, UIEventSource } from "../UIEventSource" import * as wds from "wikidata-sdk" export class WikidataResponse { @@ -131,11 +131,10 @@ export default class Wikidata { "Lexeme:", ].map((str) => str.toLowerCase()) - private static readonly _cache = new Map< + private static readonly _storeCache = new Map< string, - UIEventSource<{ success: WikidataResponse } | { error: any }> + Store<{ success: WikidataResponse } | { error: any }> >() - /** * Same as LoadWikidataEntry, but wrapped into a UIEventSource * @param value @@ -143,14 +142,14 @@ export default class Wikidata { */ public static LoadWikidataEntry( value: string | number - ): UIEventSource<{ success: WikidataResponse } | { error: any }> { + ): Store<{ success: WikidataResponse } | { error: any }> { const key = this.ExtractKey(value) - const cached = Wikidata._cache.get(key) - if (cached !== undefined) { + const cached = Wikidata._storeCache.get(key) + if (cached) { return cached } const src = UIEventSource.FromPromiseWithErr(Wikidata.LoadWikidataEntryAsync(key)) - Wikidata._cache.set(key, src) + Wikidata._storeCache.set(key, src) return src } @@ -278,6 +277,9 @@ export default class Wikidata { * * Wikidata.ExtractKey("https://www.wikidata.org/wiki/Lexeme:L614072") // => "L614072" * Wikidata.ExtractKey("http://www.wikidata.org/entity/Q55008046") // => "Q55008046" + * Wikidata.ExtractKey("Q55008046") // => "Q55008046" + * Wikidata.ExtractKey("A55008046") // => undefined + * Wikidata.ExtractKey("Q55008046X") // => undefined */ public static ExtractKey(value: string | number): string { if (typeof value === "number") { @@ -385,11 +387,24 @@ export default class Wikidata { return result.results.bindings } + private static _cache = new Map>() + public static async LoadWikidataEntryAsync(value: string | number): Promise { + const key = "" + value + const cached = Wikidata._cache.get(key) + if (cached) { + return cached + } + const uncached = Wikidata.LoadWikidataEntryUncachedAsync(value) + Wikidata._cache.set(key, uncached) + return uncached + } /** * Loads a wikidata page * @returns the entity of the given value */ - public static async LoadWikidataEntryAsync(value: string | number): Promise { + private static async LoadWikidataEntryUncachedAsync( + value: string | number + ): Promise { const id = Wikidata.ExtractKey(value) if (id === undefined) { console.warn("Could not extract a wikidata entry from", value) diff --git a/Logic/Web/Wikipedia.ts b/Logic/Web/Wikipedia.ts index 00bc1b798a..9d8b6bb094 100644 --- a/Logic/Web/Wikipedia.ts +++ b/Logic/Web/Wikipedia.ts @@ -1,9 +1,17 @@ -/** - * Some usefull utility functions around the wikipedia API - */ import { Utils } from "../../Utils" -import { UIEventSource } from "../UIEventSource" -import { WikipediaBoxOptions } from "../../UI/Wikipedia/WikipediaBox" +import Wikidata, { WikidataResponse } from "./Wikidata" +import { Store, UIEventSource } from "../UIEventSource" + +export interface FullWikipediaDetails { + articleUrl?: string + language?: string + pagename?: string + fullArticle?: string + firstParagraph?: string + restOfArticle?: string + wikidata?: WikidataResponse + title?: string +} export default class Wikipedia { /** @@ -26,11 +34,8 @@ export default class Wikipedia { private static readonly idsToRemove = ["sjabloon_zie"] - private static readonly _cache = new Map< - string, - UIEventSource<{ success: string } | { error: any }> - >() - + private static readonly _cache = new Map>() + private static _fullDetailsCache = new Map>() public readonly backend: string constructor(options?: { language?: "en" | string } | { backend?: string }) { @@ -56,23 +61,81 @@ export default class Wikipedia { } /** - * Extracts the actual pagename; returns undefined if this came from a different wikimedia entry + * Fetch all useful information for the given entity. * - * new Wikipedia({backend: "https://wiki.openstreetmap.org"}).extractPageName("https://wiki.openstreetmap.org/wiki/NL:Speelbos") // => "NL:Speelbos" - * new Wikipedia().extractPageName("https://wiki.openstreetmap.org/wiki/NL:Speelbos") // => undefined */ - public extractPageName(input: string): string | undefined { - if (!input.startsWith(this.backend)) { - return undefined + public static fetchArticleAndWikidata( + wikidataOrPageId: string, + preferedLanguage: string + ): Store { + const cachekey = preferedLanguage + wikidataOrPageId + const cached = Wikipedia._fullDetailsCache.get(cachekey) + if (cached) { + return cached } - input = input.substring(this.backend.length) + console.log("Constructing store for", cachekey) + const store = new UIEventSource({}, cachekey) + Wikipedia._fullDetailsCache.set(cachekey, store) - const matched = input.match("/?wiki/(.+)") - if (matched === undefined || matched === null) { - return undefined + // Are we dealing with a wikidata item? + const wikidataId = Wikidata.ExtractKey(wikidataOrPageId) + if (!wikidataId) { + // We are dealing with a wikipedia identifier, e.g. 'NL:articlename', 'https://nl.wikipedia.org/wiki/article', ... + const { language, pageName } = Wikipedia.extractLanguageAndName(wikidataOrPageId) + store.data.articleUrl = new Wikipedia({ language }).getPageUrl(pageName) + store.data.language = language + store.data.pagename = pageName + store.data.title = pageName + } else { + // Jup, this is a wikidata item + // Lets fetch the wikidata + store.data.title = wikidataId + Wikidata.LoadWikidataEntryAsync(wikidataId).then((wikidata) => { + store.data.wikidata = wikidata + store.ping() + // With the wikidata, we can search for the appropriate wikipedia page + const preferredLanguage = [ + preferedLanguage, + "en", + Array.from(wikidata.wikisites.keys())[0], + ] + + for (const language of preferredLanguage) { + const pagetitle = wikidata.wikisites.get(language) + if (pagetitle) { + store.data.articleUrl = new Wikipedia({ language }).getPageUrl(pagetitle) + store.data.pagename = pagetitle + store.data.language = language + store.data.title = pagetitle + store.ping() + break + } + } + }) } - const [_, pageName] = matched - return pageName + + // Now that the pageURL has been setup, we can focus on downloading the actual article + // We setup a listener. As soon as the article-URL is know, we'll fetch the actual page + // This url can either be set by the Wikidata-response or directly if we are dealing with a wikipedia-url + store.addCallbackAndRun((data) => { + if (data.language === undefined || data.pagename === undefined) { + return + } + const wikipedia = new Wikipedia({ language: data.language }) + wikipedia.GetArticleHtml(data.pagename).then((article) => { + data.fullArticle = article + const content = document.createElement("div") + content.innerHTML = article + const firstParagraph = content.getElementsByTagName("p").item(0) + data.firstParagraph = firstParagraph.innerHTML + content.removeChild(firstParagraph) + data.restOfArticle = content.innerHTML + store.ping() + }) + return true // unregister + }) + + return store } private static getBackendUrl( @@ -90,18 +153,24 @@ export default class Wikipedia { return backend } - public GetArticle( - pageName: string, - options: WikipediaBoxOptions - ): UIEventSource<{ success: string } | { error: any }> { - const key = this.backend + ":" + pageName + ":" + (options.firstParagraphOnly ?? false) - const cached = Wikipedia._cache.get(key) - if (cached !== undefined) { - return cached + /** + * Extracts the actual pagename; returns undefined if this came from a different wikimedia entry + * + * new Wikipedia({backend: "https://wiki.openstreetmap.org"}).extractPageName("https://wiki.openstreetmap.org/wiki/NL:Speelbos") // => "NL:Speelbos" + * new Wikipedia().extractPageName("https://wiki.openstreetmap.org/wiki/NL:Speelbos") // => undefined + */ + public extractPageName(input: string): string | undefined { + if (!input.startsWith(this.backend)) { + return undefined } - const v = UIEventSource.FromPromiseWithErr(this.GetArticleAsync(pageName, options)) - Wikipedia._cache.set(key, v) - return v + input = input.substring(this.backend.length) + + const matched = input.match("/?wiki/(.+)") + if (matched === undefined || matched === null) { + return undefined + } + const [_, pageName] = matched + return pageName } public getDataUrl(pageName: string): string { @@ -172,12 +241,23 @@ export default class Wikipedia { }) } - public async GetArticleAsync( - pageName: string, - options: { - firstParagraphOnly?: false | boolean + /** + * Returns the innerHTML for the given article as string. + * Some cleanup is applied to this. + * + * This method uses a static, local cache, so each article will be retrieved only once via the network + */ + public GetArticleHtml(pageName: string): Promise { + const cacheKey = this.backend + "/" + pageName + if (Wikipedia._cache.has(cacheKey)) { + return Wikipedia._cache.get(cacheKey) } - ): Promise { + const promise = this.GetArticleUncachedAsync(pageName) + Wikipedia._cache.set(cacheKey, promise) + return promise + } + + private async GetArticleUncachedAsync(pageName: string): Promise { const response = await Utils.downloadJson(this.getDataUrl(pageName)) if (response?.parse?.text === undefined) { return undefined @@ -213,10 +293,6 @@ export default class Wikipedia { link.href = `${this.backend}${link.getAttribute("href")}` }) - if (options?.firstParagraphOnly) { - return content.getElementsByTagName("p").item(0).innerHTML - } - return content.innerHTML } } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 19bf46d9a5..b36be04b00 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -44,6 +44,7 @@ import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeome import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource" import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer" +import { Utils } from "../Utils" /** * @@ -263,6 +264,10 @@ export default class ThemeViewState implements SpecialVisualizationState { } this.lastClickObject.features.setData([]) }) + + if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) { + Utils.LoadCustomCss(this.layout.customCss) + } } private initHotkeys() { @@ -371,14 +376,19 @@ export default class ThemeViewState implements SpecialVisualizationState { .get("range") ?.isDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true) + // The following layers are _not_ indexed; they trigger to much and thus trigger the metatagging + const dontInclude = new Set(["gps_location", "gps_location_history", "gps_track"]) this.layerState.filteredLayers.forEach((flayer) => { - const features: FeatureSource = specialLayers[flayer.layerDef.id] + const id = flayer.layerDef.id + const features: FeatureSource = specialLayers[id] if (features === undefined) { return } - this.featureProperties.trackFeatureSource(features) - this.indexedFeatures.addSource(features) + if (!dontInclude.has(id)) { + this.featureProperties.trackFeatureSource(features) + this.indexedFeatures.addSource(features) + } new ShowDataLayer(this.map, { features, doShowLayer: flayer.isDisplayed, diff --git a/UI/Base/ScrollableFullScreen.ts b/UI/Base/ScrollableFullScreen.ts deleted file mode 100644 index 7cb97f8313..0000000000 --- a/UI/Base/ScrollableFullScreen.ts +++ /dev/null @@ -1,163 +0,0 @@ -import Svg from "../../Svg" -import Combine from "./Combine" -import { FixedUiElement } from "./FixedUiElement" -import { UIEventSource } from "../../Logic/UIEventSource" -import Hash from "../../Logic/Web/Hash" -import BaseUIElement from "../BaseUIElement" -import Title from "./Title" -import Hotkeys from "./Hotkeys" -import Translations from "../i18n/Translations" - -/** - * - * The scrollableFullScreen is a bit of a peculiar component: - * - It shows a title and some contents, constructed from the respective functions passed into the constructor - * - When the element is 'activated', one clone of title+contents is attached to the fullscreen - * - The element itself will - upon rendering - also show the title and contents (allthough it'll be a different clone) - * - * - */ -export default class ScrollableFullScreen { - private static readonly empty = ScrollableFullScreen.initEmpty() - private static _currentlyOpen: ScrollableFullScreen - public isShown: UIEventSource - private hashToShow: string - private _fullscreencomponent: BaseUIElement - private _resetScrollSignal: UIEventSource = new UIEventSource(undefined) - private _setHash: boolean - - constructor( - title: (options: { mode: string }) => BaseUIElement, - content: (options: { - mode: string - resetScrollSignal: UIEventSource - }) => BaseUIElement, - hashToShow: string, - isShown: UIEventSource = new UIEventSource(false), - options?: { - setHash?: boolean - } - ) { - this.hashToShow = hashToShow - this.isShown = isShown - this._setHash = options?.setHash ?? true - - if ((hashToShow === undefined || hashToShow === "") && this._setHash) { - throw "HashToShow should be defined as it is vital for the 'back' key functionality" - } - - const mobileOptions = { - mode: "mobile", - resetScrollSignal: this._resetScrollSignal, - } - - this._fullscreencomponent = this.BuildComponent( - title(mobileOptions), - content(mobileOptions).SetClass("pb-20") - ) - - const self = this - if (this._setHash) { - Hash.hash.addCallback((h) => { - if (h === undefined) { - isShown.setData(false) - } - }) - } - - isShown.addCallbackD((isShown) => { - if (isShown) { - // We first must set the hash, then activate the panel - // If the order is wrong, this will cause the panel to disactivate again - ScrollableFullScreen._currentlyOpen = self - self.Activate() - } else { - if (self.hashToShow !== undefined) { - Hash.hash.setData(undefined) - } - // Some cleanup... - ScrollableFullScreen.collapse() - } - }) - if (isShown.data) { - ScrollableFullScreen._currentlyOpen = self - this.Activate() - } - } - - private static initEmpty(): FixedUiElement { - Hotkeys.RegisterHotkey( - { nomod: "Escape", onUp: true }, - Translations.t.hotkeyDocumentation.closeSidebar, - ScrollableFullScreen.collapse - ) - - return new FixedUiElement("") - } - public static collapse() { - const fs = document.getElementById("fullscreen") - if (fs !== null) { - ScrollableFullScreen.empty.AttachTo("fullscreen") - fs.classList.add("hidden") - } - - const opened = ScrollableFullScreen._currentlyOpen - if (opened !== undefined) { - opened?.isShown?.setData(false) - } - } - - - /** - * Actually show this in the 'fullscreen'-div - * @constructor - */ - public Activate(): void { - if (this.hashToShow && this.hashToShow !== "" && this._setHash) { - Hash.hash.setData(this.hashToShow) - } - this.isShown.setData(true) - this._fullscreencomponent.AttachTo("fullscreen") - const fs = document.getElementById("fullscreen") - ScrollableFullScreen._currentlyOpen = this - fs?.classList?.remove("hidden") - } - - private BuildComponent(title: BaseUIElement, content: BaseUIElement): BaseUIElement { - const returnToTheMap = new Combine([ - Svg.back_svg().SetClass("block md:hidden w-12 h-12 p-2 svg-foreground"), - Svg.close_svg().SetClass("hidden md:block w-12 h-12 p-3 svg-foreground"), - ]).SetClass("rounded-full p-0 flex-shrink-0 self-center") - - returnToTheMap.onClick(() => { - this.isShown.setData(false) - Hash.hash.setData(undefined) - }) - - title = new Title(title, 2) - title.SetClass( - "text-l sm:text-xl md:text-2xl w-full p-0 max-h-20vh overflow-y-auto self-center" - ) - - const contentWrapper = new Combine([content]).SetClass( - "block p-2 md:pt-4 w-full h-full overflow-y-auto" - ) - - this._resetScrollSignal.addCallback((_) => { - contentWrapper.ScrollToTop() - }) - - return new Combine([ - new Combine([ - new Combine([returnToTheMap, title]).SetClass( - "border-b-1 border-black shadow bg-white flex flex-shrink-0 pt-1 pb-1 md:pt-0 md:pb-0" - ), - contentWrapper, - // We add an ornament which takes around 5em. This is in order to make sure the Web UI doesn't hide - ]).SetClass("flex flex-col h-full relative bg-white"), - ]).SetClass( - "fixed top-0 left-0 right-0 h-screen w-screen md:w-auto md:relative z-above-controls md:rounded-xl overflow-hidden" - ) - } - -} diff --git a/UI/Base/TabbedComponent.ts b/UI/Base/TabbedComponent.ts deleted file mode 100644 index 4e3eff50fc..0000000000 --- a/UI/Base/TabbedComponent.ts +++ /dev/null @@ -1,57 +0,0 @@ -import Translations from "../i18n/Translations" -import { UIEventSource } from "../../Logic/UIEventSource" -import Combine from "./Combine" -import BaseUIElement from "../BaseUIElement" -import { VariableUiElement } from "./VariableUIElement" - -export class TabbedComponent extends Combine { - /** - * @deprecated - */ - constructor( - elements: { header: BaseUIElement | string; content: BaseUIElement | string }[], - openedTab: UIEventSource | number = 0, - options?: { - leftOfHeader?: BaseUIElement - styleHeader?: (header: BaseUIElement) => void - } - ) { - const openedTabSrc = - typeof openedTab === "number" - ? new UIEventSource(openedTab) - : openedTab ?? new UIEventSource(0) - - const tabs: BaseUIElement[] = [options?.leftOfHeader] - const contentElements: BaseUIElement[] = [] - for (let i = 0; i < elements.length; i++) { - let element = elements[i] - const header = Translations.W(element.header).onClick(() => openedTabSrc.setData(i)) - openedTabSrc.addCallbackAndRun((selected) => { - if (selected >= elements.length) { - selected = 0 - } - if (selected === i) { - header.SetClass("tab-active") - header.RemoveClass("tab-non-active") - } else { - header.SetClass("tab-non-active") - header.RemoveClass("tab-active") - } - }) - const content = Translations.W(element.content) - content.SetClass("relative w-full inline-block") - contentElements.push(content) - const tab = header.SetClass("block tab-single-header") - tabs.push(tab) - } - - const header = new Combine(tabs).SetClass("tabs-header-bar") - if (options?.styleHeader) { - options.styleHeader(header) - } - const actualContent = new VariableUiElement( - openedTabSrc.map((i) => contentElements[i]) - ).SetStyle("max-height: inherit; height: inherit") - super([header, actualContent]) - } -} diff --git a/UI/Base/TabbedGroup.svelte b/UI/Base/TabbedGroup.svelte index 86c7bbab7f..9f0d5e169b 100644 --- a/UI/Base/TabbedGroup.svelte +++ b/UI/Base/TabbedGroup.svelte @@ -66,3 +66,15 @@ + + diff --git a/UI/BigComponents/AllDownloads.ts b/UI/BigComponents/AllDownloads.ts index 33ea33a495..20c454b7b7 100644 --- a/UI/BigComponents/AllDownloads.ts +++ b/UI/BigComponents/AllDownloads.ts @@ -1,62 +1,33 @@ import Combine from "../Base/Combine" -import ScrollableFullScreen from "../Base/ScrollableFullScreen" import Translations from "../i18n/Translations" import { UIEventSource } from "../../Logic/UIEventSource" -import BaseUIElement from "../BaseUIElement" import Toggle from "../Input/Toggle" import { SubtleButton } from "../Base/SubtleButton" import Svg from "../../Svg" import ExportPDF from "../ExportPDF" import FilteredLayer from "../../Models/FilteredLayer" -import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { BBox } from "../../Logic/BBox" -import BaseLayer from "../../Models/BaseLayer" import Loc from "../../Models/Loc" -interface DownloadState { - filteredLayers: UIEventSource - featurePipeline: FeaturePipeline - layoutToUse: LayoutConfig - currentBounds: UIEventSource - backgroundLayer: UIEventSource - locationControl: UIEventSource - featureSwitchExportAsPdf: UIEventSource - featureSwitchEnableExport: UIEventSource -} - -export default class AllDownloads extends ScrollableFullScreen { +export default class AllDownloads extends SubtleButton { constructor( isShown: UIEventSource, state: { filteredLayers: UIEventSource - featurePipeline: FeaturePipeline layoutToUse: LayoutConfig currentBounds: UIEventSource - backgroundLayer: UIEventSource locationControl: UIEventSource featureSwitchExportAsPdf: UIEventSource featureSwitchEnableExport: UIEventSource } ) { - super(AllDownloads.GenTitle, () => AllDownloads.GeneratePanel(state), "downloads", isShown) - } - - private static GenTitle(): BaseUIElement { - return Translations.t.general.download.title - .Clone() - .SetClass("text-2xl break-words font-bold p-2") - } - - private static GeneratePanel(state: DownloadState): BaseUIElement { const isExporting = new UIEventSource(false, "Pdf-is-exporting") const generatePdf = () => { isExporting.setData(true) new ExportPDF({ freeDivId: "belowmap", - background: state.backgroundLayer, location: state.locationControl, - features: state.featurePipeline, layout: state.layoutToUse, }).isRunning.addCallbackAndRun((isRunning) => isExporting.setData(isRunning)) } @@ -78,6 +49,6 @@ export default class AllDownloads extends ScrollableFullScreen { isExporting ) - return new SubtleButton(icon, text) + super(icon, text) } } diff --git a/UI/BigComponents/LeftControls.ts b/UI/BigComponents/LeftControls.ts index 4021c23d8d..a0e71a5c42 100644 --- a/UI/BigComponents/LeftControls.ts +++ b/UI/BigComponents/LeftControls.ts @@ -6,8 +6,6 @@ import AllDownloads from "./AllDownloads" import { Store, UIEventSource } from "../../Logic/UIEventSource" import Lazy from "../Base/Lazy" import { VariableUiElement } from "../Base/VariableUIElement" -import FeatureInfoBox from "../Popup/FeatureInfoBox" -import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import { DefaultGuiState } from "../DefaultGuiState" export default class LeftControls extends Combine { @@ -57,8 +55,6 @@ export default class LeftControls extends Combine { new AllDownloads(guiState.downloadControlIsOpened, state) - - super([currentViewAction]) this.SetClass("flex flex-col") diff --git a/UI/BigComponents/PlantNetSpeciesSearch.ts b/UI/BigComponents/PlantNetSpeciesSearch.ts index 5b133def28..b7e503f6e0 100644 --- a/UI/BigComponents/PlantNetSpeciesSearch.ts +++ b/UI/BigComponents/PlantNetSpeciesSearch.ts @@ -7,7 +7,6 @@ import WikidataPreviewBox from "../Wikipedia/WikidataPreviewBox" import { Button } from "../Base/Button" import Combine from "../Base/Combine" import Title from "../Base/Title" -import WikipediaBox from "../Wikipedia/WikipediaBox" import Translations from "../i18n/Translations" import List from "../Base/List" import Svg from "../../Svg" @@ -97,7 +96,7 @@ export default class PlantNetSpeciesSearch extends VariableUiElement { if (wikidataSpecies === undefined) { return plantOverview } - const buttons = new Combine([ + return new Combine([ new Button( new Combine([ Svg.back_svg().SetClass( @@ -120,15 +119,6 @@ export default class PlantNetSpeciesSearch extends VariableUiElement { } ).SetClass("btn"), ]).SetClass("flex justify-between") - - return new Combine([ - new WikipediaBox([wikidataSpecies], { - firstParagraphOnly: false, - noImages: false, - addHeader: false, - }).SetClass("h-96"), - buttons, - ]).SetClass("flex flex-col self-end") }) ) }) diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index a5b27c22d2..c5d08a8e7e 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -1,15 +1,11 @@ -import { Utils } from "../Utils" import Toggle from "./Input/Toggle" import LeftControls from "./BigComponents/LeftControls" import RightControls from "./BigComponents/RightControls" import CenterMessageBox from "./CenterMessageBox" -import ScrollableFullScreen from "./Base/ScrollableFullScreen" -import Translations from "./i18n/Translations" import { DefaultGuiState } from "./DefaultGuiState" import Combine from "./Base/Combine" import ExtraLinkButton from "./BigComponents/ExtraLinkButton" import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" -import CopyrightPanel from "./BigComponents/CopyrightPanel" /** * The default MapComplete GUI initializer @@ -25,17 +21,6 @@ export default class DefaultGUI { } public setup() { - this.SetupUIElements() - - if ( - this.state.layoutToUse.customCss !== undefined && - window.location.pathname.indexOf("index") >= 0 - ) { - Utils.LoadCustomCss(this.state.layoutToUse.customCss) - } - } - - private SetupUIElements() { const extraLink = Toggle.If( state.featureSwitchExtraLinkEnabled, () => new ExtraLinkButton(state, state.layoutToUse.extraLink) diff --git a/UI/Image/ImageUploadFlow.ts b/UI/Image/ImageUploadFlow.ts index a2457bf078..6d6c139527 100644 --- a/UI/Image/ImageUploadFlow.ts +++ b/UI/Image/ImageUploadFlow.ts @@ -14,8 +14,6 @@ import { VariableUiElement } from "../Base/VariableUIElement" import Loading from "../Base/Loading" import { LoginToggle } from "../Popup/LoginButton" import Constants from "../../Models/Constants" -import { DefaultGuiState } from "../DefaultGuiState" -import ScrollableFullScreen from "../Base/ScrollableFullScreen" import { SpecialVisualizationState } from "../SpecialVisualization" export class ImageUploadFlow extends Toggle { diff --git a/UI/ImportFlow/MapPreview.ts b/UI/ImportFlow/MapPreview.ts index 929a2ed0ff..854486044d 100644 --- a/UI/ImportFlow/MapPreview.ts +++ b/UI/ImportFlow/MapPreview.ts @@ -11,33 +11,15 @@ import Loc from "../../Models/Loc" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import Toggle from "../Input/Toggle" import { VariableUiElement } from "../Base/VariableUIElement" -import { FixedUiElement } from "../Base/FixedUiElement" import { FlowStep } from "./FlowStep" -import ScrollableFullScreen from "../Base/ScrollableFullScreen" import Title from "../Base/Title" import CheckBoxes from "../Input/Checkboxes" -import AllTagsPanel from "../Popup/AllTagsPanel.svelte" import { Feature, Point } from "geojson" import DivContainer from "../Base/DivContainer" -import SvelteUIElement from "../Base/SvelteUIElement" import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers" import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" import ShowDataLayer from "../Map/ShowDataLayer" -class PreviewPanel extends ScrollableFullScreen { - constructor(tags: UIEventSource) { - super( - (_) => new FixedUiElement("Element to import"), - (_) => - new Combine([ - "The tags are:", - new SvelteUIElement(AllTagsPanel, { tags }), - ]).SetClass("flex flex-col"), - "element" - ) - } -} - /** * Shows the data to import on a map, asks for the correct layer to be selected */ @@ -111,7 +93,6 @@ export class MapPreview const currentBounds = new UIEventSource(undefined) const { ui, mapproperties, map } = MapLibreAdaptor.construct() - ui.SetClass("w-full").SetStyle("height: 500px") layerPicker.GetValue().addCallbackAndRunD((layerToShow) => { @@ -119,7 +100,6 @@ export class MapPreview layer: layerToShow, zoomToFeatures: true, features: new StaticFeatureSource(matching), - buildPopup: (tag) => new PreviewPanel(tag), }) }) diff --git a/UI/InputElement/Validators/WikidataValidator.ts b/UI/InputElement/Validators/WikidataValidator.ts index cb031f4477..53eac4e708 100644 --- a/UI/InputElement/Validators/WikidataValidator.ts +++ b/UI/InputElement/Validators/WikidataValidator.ts @@ -34,62 +34,4 @@ export default class WikidataValidator extends Validator { } return out } - - public inputHelper(currentValue, inputHelperOptions) { - const args = inputHelperOptions.args ?? [] - const searchKey = args[0] ?? "name" - - const searchFor = ( - (inputHelperOptions.feature?.properties[searchKey]?.toLowerCase() ?? "") - ) - - let searchForValue: UIEventSource = new UIEventSource(searchFor) - const options: any = args[1] - if (searchFor !== undefined && options !== undefined) { - const prefixes = >options["removePrefixes"] ?? [] - const postfixes = >options["removePostfixes"] ?? [] - const defaultValueCandidate = Locale.language.map((lg) => { - const prefixesUnrwapped: RegExp[] = ( - Array.isArray(prefixes) ? prefixes : prefixes[lg] ?? [] - ).map((s) => new RegExp("^" + s, "i")) - const postfixesUnwrapped: RegExp[] = ( - Array.isArray(postfixes) ? postfixes : postfixes[lg] ?? [] - ).map((s) => new RegExp(s + "$", "i")) - let clipped = searchFor - - for (const postfix of postfixesUnwrapped) { - const match = searchFor.match(postfix) - if (match !== null) { - clipped = searchFor.substring(0, searchFor.length - match[0].length) - break - } - } - - for (const prefix of prefixesUnrwapped) { - const match = searchFor.match(prefix) - if (match !== null) { - clipped = searchFor.substring(match[0].length) - break - } - } - return clipped - }) - - defaultValueCandidate.addCallbackAndRun((clipped) => searchForValue.setData(clipped)) - } - - let instanceOf: number[] = Utils.NoNull( - (options?.instanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)) - ) - let notInstanceOf: number[] = Utils.NoNull( - (options?.notInstanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)) - ) - - return new WikidataSearchBox({ - value: currentValue, - searchText: searchForValue, - instanceOf, - notInstanceOf, - }) - } } diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts deleted file mode 100644 index 8b358059e7..0000000000 --- a/UI/Popup/FeatureInfoBox.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { UIEventSource } from "../../Logic/UIEventSource" -import Combine from "../Base/Combine" -import ScrollableFullScreen from "../Base/ScrollableFullScreen" -import BaseUIElement from "../BaseUIElement" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import Toggle from "../Input/Toggle" -import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" -import Svg from "../../Svg" -import Translations from "../i18n/Translations" - -export default class FeatureInfoBox extends ScrollableFullScreen { - public constructor( - tags: UIEventSource, - layerConfig: LayerConfig, - state: FeaturePipelineState, - options?: { - hashToShow?: string - isShown?: UIEventSource - setHash?: true | boolean - } - ) { - const showAllQuestions = state.featureSwitchShowAllQuestions.map( - (fsShow) => fsShow || state.showAllQuestionsAtOnce.data, - [state.showAllQuestionsAtOnce] - ) - super( - () => undefined, - () => FeatureInfoBox.GenerateContent(tags, layerConfig), - options?.hashToShow ?? tags.data.id ?? "item", - options?.isShown, - options - ) - - if (layerConfig === undefined) { - throw "Undefined layerconfig" - } - } - - public static GenerateContent(tags: UIEventSource): BaseUIElement { - return new Toggle( - new Combine([ - Svg.delete_icon_svg().SetClass("w-8 h-8"), - Translations.t.delete.isDeleted, - ]).SetClass("flex justify-center font-bold items-center"), - new Combine([]).SetClass("block"), - tags.map((t) => t["_deleted"] == "yes") - ) - } -} diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index b556136c37..a9d62655b0 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -19,14 +19,13 @@ import { ConflateButton, ImportPointButton, ImportWayButton } from "./Popup/Impo import TagApplyButton from "./Popup/TagApplyButton" import { CloseNoteButton } from "./Popup/CloseNoteButton" import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis" -import { Stores, UIEventSource } from "../Logic/UIEventSource" +import { Store, Stores, UIEventSource } from "../Logic/UIEventSource" import AllTagsPanel from "./Popup/AllTagsPanel.svelte" import AllImageProviders from "../Logic/ImageProviders/AllImageProviders" import { ImageCarousel } from "./Image/ImageCarousel" import { ImageUploadFlow } from "./Image/ImageUploadFlow" import { VariableUiElement } from "./Base/VariableUIElement" import { Utils } from "../Utils" -import WikipediaBox from "./Wikipedia/WikipediaBox" import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata" import { Translation } from "./i18n/Translation" import Translations from "./i18n/Translations" @@ -80,6 +79,7 @@ import { OsmId, OsmTags, WayId } from "../Models/OsmFeature" import MoveWizard from "./Popup/MoveWizard" import SplitRoadWizard from "./Popup/SplitRoadWizard" import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" +import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte" class NearbyImageVis implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -628,7 +628,7 @@ export default class SpecialVisualizations { { funcName: "wikipedia", - docs: "A box showing the corresponding wikipedia article - based on the wikidata tag", + docs: "A box showing the corresponding wikipedia article(s) - based on the **wikidata** tag.", args: [ { name: "keyToShowWikipediaFor", @@ -638,23 +638,15 @@ export default class SpecialVisualizations { ], example: "`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height", - constr: (_, tagsSource, args) => { + constr: (_, tagsSource, args, feature, layer) => { const keys = args[0].split(";").map((k) => k.trim()) - return new VariableUiElement( - tagsSource - .map((tags) => { - const key = keys.find( - (k) => tags[k] !== undefined && tags[k] !== "" - ) - return tags[key] - }) - .map((wikidata) => { - const wikidatas: string[] = Utils.NoEmpty( - wikidata?.split(";")?.map((wd) => wd.trim()) ?? [] - ) - return new WikipediaBox(wikidatas) - }) - ) + const wikiIds: Store = tagsSource.map((tags) => { + const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "") + return tags[key]?.split(";")?.map((id) => id.trim()) + }) + return new SvelteUIElement(WikipediaPanel, { + wikiIds, + }) }, }, { diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 461efe048a..10b45698c7 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -276,17 +276,3 @@ - - diff --git a/UI/Wikipedia/WikidataSearchBox.ts b/UI/Wikipedia/WikidataSearchBox.ts index d411031aaa..070d25e6ec 100644 --- a/UI/Wikipedia/WikidataSearchBox.ts +++ b/UI/Wikipedia/WikidataSearchBox.ts @@ -8,20 +8,12 @@ import Locale from "../i18n/Locale" import { VariableUiElement } from "../Base/VariableUIElement" import WikidataPreviewBox from "./WikidataPreviewBox" import Title from "../Base/Title" -import WikipediaBox from "./WikipediaBox" import Svg from "../../Svg" import Loading from "../Base/Loading" import Table from "../Base/Table" export default class WikidataSearchBox extends InputElement { - private static readonly _searchCache = new Map>() - private readonly wikidataId: UIEventSource - private readonly searchText: UIEventSource - private readonly instanceOf?: number[] - private readonly notInstanceOf?: number[] - public static docs = new Combine([ - , new Title("Helper arguments"), new Table( ["name", "doc"], @@ -100,6 +92,11 @@ Another example is to search for species and trees: \`\`\` `, ]) + private static readonly _searchCache = new Map>() + private readonly wikidataId: UIEventSource + private readonly searchText: UIEventSource + private readonly instanceOf?: number[] + private readonly notInstanceOf?: number[] constructor(options?: { searchText?: UIEventSource @@ -207,25 +204,15 @@ Another example is to search for species and trees: ) ) - const full = new Combine([ + return new Combine([ new Title(Translations.t.general.wikipedia.searchWikidata, 3).SetClass("m-2"), new Combine([ Svg.search_ui().SetStyle("width: 1.5rem"), searchField.SetClass("m-2 w-full"), ]).SetClass("flex"), previews, - ]).SetClass("flex flex-col border-2 border-black rounded-xl m-2 p-2") - - return new Combine([ - new VariableUiElement( - selectedWikidataId.map((wid) => { - if (wid === undefined) { - return undefined - } - return new WikipediaBox(wid.split(";")) - }) - ).SetStyle("max-height:12.5rem"), - full, - ]).ConstructElement() + ]) + .SetClass("flex flex-col border-2 border-black rounded-xl m-2 p-2") + .ConstructElement() } } diff --git a/UI/Wikipedia/WikipediaArticle.svelte b/UI/Wikipedia/WikipediaArticle.svelte new file mode 100644 index 0000000000..e7067b4b80 --- /dev/null +++ b/UI/Wikipedia/WikipediaArticle.svelte @@ -0,0 +1,46 @@ + + + + + + + +{#if $wikipediaDetails.wikidata} + +{/if} + +{#if $wikipediaDetails.firstParagraph === "" || $wikipediaDetails.firstParagraph === undefined} + + + +{:else} + + + + + + Read the rest of the article + + + + + + + +{/if} diff --git a/UI/Wikipedia/WikipediaBox.ts b/UI/Wikipedia/WikipediaBox.ts deleted file mode 100644 index 0ce23fb13b..0000000000 --- a/UI/Wikipedia/WikipediaBox.ts +++ /dev/null @@ -1,321 +0,0 @@ -import BaseUIElement from "../BaseUIElement" -import Locale from "../i18n/Locale" -import { VariableUiElement } from "../Base/VariableUIElement" -import { Translation } from "../i18n/Translation" -import Svg from "../../Svg" -import Combine from "../Base/Combine" -import Title from "../Base/Title" -import Wikipedia from "../../Logic/Web/Wikipedia" -import Wikidata, { WikidataResponse } from "../../Logic/Web/Wikidata" -import { TabbedComponent } from "../Base/TabbedComponent" -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import Loading from "../Base/Loading" -import { FixedUiElement } from "../Base/FixedUiElement" -import Translations from "../i18n/Translations" -import Link from "../Base/Link" -import WikidataPreviewBox from "./WikidataPreviewBox" -import { Paragraph } from "../Base/Paragraph" - -export interface WikipediaBoxOptions { - addHeader: boolean - firstParagraphOnly: boolean - noImages: boolean - currentState?: UIEventSource<"loading" | "loaded" | "error"> -} - -export default class WikipediaBox extends Combine { - constructor(wikidataIds: string[], options?: WikipediaBoxOptions) { - const mainContents = [] - options = options ?? { addHeader: false, firstParagraphOnly: true, noImages: false } - const pages = wikidataIds.map((entry) => - WikipediaBox.createLinkedContent(entry.trim(), options) - ) - if (wikidataIds.length == 1) { - const page = pages[0] - mainContents.push( - new Combine([ - new Combine([ - options.noImages - ? undefined - : Svg.wikipedia_ui() - .SetStyle("width: 1.5rem") - .SetClass("inline-block mr-3"), - page.titleElement, - ]).SetClass("flex"), - page.linkElement, - ]).SetClass("flex justify-between align-middle") - ) - mainContents.push(page.contents.SetClass("overflow-auto normal-background rounded-lg")) - } else if (wikidataIds.length > 1) { - const tabbed = new TabbedComponent( - pages.map((page) => { - const contents = page.contents - .SetClass("overflow-auto normal-background rounded-lg block") - .SetStyle("max-height: inherit; height: inherit; padding-bottom: 3.3rem") - return { - header: page.titleElement.SetClass("pl-2 pr-2"), - content: new Combine([ - page.linkElement - .SetStyle("top: 2rem; right: 2.5rem;") - .SetClass( - "absolute subtle-background rounded-full p-3 opacity-50 hover:opacity-100 transition-opacity" - ), - contents, - ]) - .SetStyle("max-height: inherit; height: inherit") - .SetClass("relative"), - } - }), - 0, - { - leftOfHeader: options.noImages - ? undefined - : Svg.wikipedia_svg() - .SetStyle("width: 1.5rem; align-self: center;") - .SetClass("mr-4"), - styleHeader: (header) => - header.SetClass("subtle-background").SetStyle("height: 3.3rem"), - } - ) - tabbed.SetStyle("height: inherit; max-height: inherit; overflow: hidden") - mainContents.push(tabbed) - } - - super(mainContents) - - this.SetClass("block rounded-xl subtle-background m-1 p-2 flex flex-col").SetStyle( - "max-height: inherit" - ) - } - - private static createLinkedContent( - entry: string, - options: WikipediaBoxOptions - ): { - titleElement: BaseUIElement - contents: BaseUIElement - linkElement: BaseUIElement - } { - if (entry.match("[qQ][0-9]+")) { - return WikipediaBox.createWikidatabox(entry, options) - } else { - return WikipediaBox.createWikipediabox(entry, options) - } - } - - /** - * Given a ':'-string, constructs the wikipedia article - */ - private static createWikipediabox( - wikipediaArticle: string, - options: WikipediaBoxOptions - ): { - titleElement: BaseUIElement - contents: BaseUIElement - linkElement: BaseUIElement - } { - const wp = Translations.t.general.wikipedia - - const article = Wikipedia.extractLanguageAndName(wikipediaArticle) - if (article === undefined) { - return { - titleElement: undefined, - contents: wp.noWikipediaPage, - linkElement: undefined, - } - } - const wikipedia = new Wikipedia({ language: article.language }) - const url = wikipedia.getPageUrl(article.pageName) - const linkElement = new Link( - Svg.pop_out_svg().SetStyle("width: 1.2rem").SetClass("block "), - url, - true - ).SetClass("flex items-center enable-links") - - return { - titleElement: new Title(article.pageName, 3), - contents: WikipediaBox.createContents(article.pageName, wikipedia, options), - linkElement, - } - } - - /** - * Given a `Q1234`, constructs a wikipedia box (if a wikipedia page is available) or wikidata box as fallback. - * - */ - private static createWikidatabox( - wikidataId: string, - options: WikipediaBoxOptions - ): { - titleElement: BaseUIElement - contents: BaseUIElement - linkElement: BaseUIElement - } { - const wp = Translations.t.general.wikipedia - - const wikiLink: Store< - | [string, string, WikidataResponse] - | "loading" - | "failed" - | ["no page", WikidataResponse] - > = Wikidata.LoadWikidataEntry(wikidataId).map( - (maybewikidata) => { - if (maybewikidata === undefined) { - return "loading" - } - if (maybewikidata["error"] !== undefined) { - return "failed" - } - const wikidata = maybewikidata["success"] - if (wikidata === undefined) { - return "failed" - } - if (wikidata.wikisites.size === 0) { - return ["no page", wikidata] - } - - const preferredLanguage = [ - Locale.language.data, - "en", - Array.from(wikidata.wikisites.keys())[0], - ] - let language - let pagetitle - let i = 0 - do { - language = preferredLanguage[i] - pagetitle = wikidata.wikisites.get(language) - i++ - } while (pagetitle === undefined) - return [pagetitle, language, wikidata] - }, - [Locale.language] - ) - - const contents = new VariableUiElement( - wikiLink.map((status) => { - if (status === "loading") { - return new Loading(wp.loading.Clone()).SetClass("pl-6 pt-2") - } - - if (status === "failed") { - return wp.failed.Clone().SetClass("alert p-4") - } - if (status[0] == "no page") { - const [_, wd] = <[string, WikidataResponse]>status - options.currentState?.setData("loaded") - return new Combine([ - WikidataPreviewBox.WikidataResponsePreview(wd), - wp.noWikipediaPage.Clone().SetClass("subtle"), - ]).SetClass("flex flex-col p-4") - } - - const [pagetitle, language, wd] = <[string, string, WikidataResponse]>status - const wikipedia = new Wikipedia({ language }) - const quickFacts = WikidataPreviewBox.QuickFacts(wd) - return WikipediaBox.createContents(pagetitle, wikipedia, { - topBar: quickFacts, - ...options, - }) - }) - ) - - const titleElement = new VariableUiElement( - wikiLink.map((state) => { - if (typeof state !== "string") { - const [pagetitle, _] = state - if (pagetitle === "no page") { - const wd = state[1] - return new Title(Translation.fromMap(wd.labels), 3) - } - return new Title(pagetitle, 3) - } - return new Link( - new Title(wikidataId, 3), - "https://www.wikidata.org/wiki/" + wikidataId, - true - ) - }) - ) - - const linkElement = new VariableUiElement( - wikiLink.map((state) => { - if (typeof state !== "string") { - const [pagetitle, language] = state - const popout = options.noImages - ? "Source" - : Svg.pop_out_svg().SetStyle("width: 1.2rem").SetClass("block") - if (pagetitle === "no page") { - const wd = state[1] - return new Link(popout, "https://www.wikidata.org/wiki/" + wd.id, true) - } - - const url = `https://${language}.wikipedia.org/wiki/${pagetitle}` - return new Link(popout, url, true) - } - return undefined - }) - ).SetClass("flex items-center enable-links") - - return { - contents: contents, - linkElement: linkElement, - titleElement: titleElement, - } - } - - /** - * Returns the actual content in a scrollable way for the given wikipedia page - */ - private static createContents( - pagename: string, - wikipedia: Wikipedia, - options: { - topBar?: BaseUIElement - } & WikipediaBoxOptions - ): BaseUIElement { - const htmlContent = wikipedia.GetArticle(pagename, options) - const wp = Translations.t.general.wikipedia - const contents: VariableUiElement = new VariableUiElement( - htmlContent.map((htmlContent) => { - if (htmlContent === undefined) { - // Still loading - return new Loading(wp.loading.Clone()) - } - if (htmlContent["success"] !== undefined) { - let content: BaseUIElement = new FixedUiElement(htmlContent["success"]) - if (options?.addHeader) { - content = new Combine([ - new Paragraph( - new Link(wp.fromWikipedia, wikipedia.getPageUrl(pagename), true) - ), - new Paragraph(content), - ]) - } - return content.SetClass("wikipedia-article") - } - if (htmlContent["error"]) { - console.warn("Loading wikipage failed due to", htmlContent["error"]) - return wp.failed.Clone().SetClass("alert p-4") - } - - return undefined - }) - ) - - htmlContent.addCallbackAndRunD((c) => { - if (c["success"] !== undefined) { - options.currentState?.setData("loaded") - } else if (c["error"] !== undefined) { - options.currentState?.setData("error") - } else { - options.currentState?.setData("loading") - } - }) - - return new Combine([ - options?.topBar?.SetClass("border-2 border-grey rounded-lg m-1 mb-0"), - contents.SetClass("block pl-6 pt-2"), - ]) - } -} diff --git a/UI/Wikipedia/WikipediaBoxOptions.ts b/UI/Wikipedia/WikipediaBoxOptions.ts new file mode 100644 index 0000000000..52a81c8ea8 --- /dev/null +++ b/UI/Wikipedia/WikipediaBoxOptions.ts @@ -0,0 +1,7 @@ +import { UIEventSource } from "../../Logic/UIEventSource" + +export interface WikipediaBoxOptions { + addHeader?: boolean + firstParagraphOnly?: true | boolean + allowToAdd?: boolean +} diff --git a/UI/Wikipedia/WikipediaPanel.svelte b/UI/Wikipedia/WikipediaPanel.svelte new file mode 100644 index 0000000000..335d76673e --- /dev/null +++ b/UI/Wikipedia/WikipediaPanel.svelte @@ -0,0 +1,56 @@ + +{#if _wikipediaStores !== undefined} + + + {#each _wikipediaStores as store (store.tag)} + selected ? "tab-selected" : "tab-unselected"}> + + + {/each} + + + + {#each _wikipediaStores as store (store.tag)} + + + + + {/each} + + +{/if} + diff --git a/UI/Wikipedia/WikipediaTitle.svelte b/UI/Wikipedia/WikipediaTitle.svelte new file mode 100644 index 0000000000..1255a7603b --- /dev/null +++ b/UI/Wikipedia/WikipediaTitle.svelte @@ -0,0 +1,13 @@ + + +{$wikipediaDetails.title} + diff --git a/assets/layers/gps_track/gps_track.json b/assets/layers/gps_track/gps_track.json index 94bb2a40b8..eb0110171c 100644 --- a/assets/layers/gps_track/gps_track.json +++ b/assets/layers/gps_track/gps_track.json @@ -44,4 +44,4 @@ } ], "syncSelection": "global" -} +} \ No newline at end of file diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index 985db6ddc3..65526d7d91 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -2110,4 +2110,4 @@ } ] } -} +} \ No newline at end of file diff --git a/assets/themes/uk_addresses/uk_addresses.json b/assets/themes/uk_addresses/uk_addresses.json index b39453b08e..2c9ca9562e 100644 --- a/assets/themes/uk_addresses/uk_addresses.json +++ b/assets/themes/uk_addresses/uk_addresses.json @@ -695,4 +695,4 @@ "enableShareScreen": false, "enableMoreQuests": false, "credits": "Pieter Vander Vennet, Rob Nickerson, Russ Garrett" -} +} \ No newline at end of file diff --git a/langs/en.json b/langs/en.json index b744bdf892..dc34045bc0 100644 --- a/langs/en.json +++ b/langs/en.json @@ -362,6 +362,7 @@ "general": "On this map, you can see, edit and add points of interest. Zoom around to see the POI, tap one to see or edit the information. All data is sourced from and saved to OpenStreetMap, which can be freely reused." }, "wikipedia": { + "addEntry": "Add another Wikipedia page", "createNewWikidata": "Create a new Wikidata item", "doSearch": "Search above to see results", "failed": "Loading the Wikipedia entry failed", diff --git a/langs/shared-questions/ca.json b/langs/shared-questions/ca.json index 9e8db263ac..690d20012d 100644 --- a/langs/shared-questions/ca.json +++ b/langs/shared-questions/ca.json @@ -131,6 +131,13 @@ "question": "Quin és el nom de la xarxa per a l'accés inalàmbric a internet?", "render": "El nom de la xarxa és {internet_access:ssid}" }, + "just_created": { + "mappings": { + "0": { + "then": "Acabeu de crear aquest element! Gràcies per compartir aquesta informació amb el mon i ajudar a persones al voltant del món." + } + } + }, "level": { "mappings": { "0": { diff --git a/langs/shared-questions/de.json b/langs/shared-questions/de.json index 5e67ec1342..2d36b79389 100644 --- a/langs/shared-questions/de.json +++ b/langs/shared-questions/de.json @@ -131,6 +131,13 @@ "question": "Wie lautet der Netzwerkname für den drahtlosen Internetzugang?", "render": "Der Netzwerkname lautet {internet_access:ssid}" }, + "just_created": { + "mappings": { + "0": { + "then": "Sie haben gerade dieses Element erstellt! Vielen Dank, dass Sie diese Informationen mit der Welt teilen und Menschen weltweit helfen." + } + } + }, "level": { "mappings": { "0": { diff --git a/langs/shared-questions/en.json b/langs/shared-questions/en.json index 2ceea52424..1ac9eee069 100644 --- a/langs/shared-questions/en.json +++ b/langs/shared-questions/en.json @@ -131,6 +131,13 @@ "question": "What is the network name for the wireless internet access?", "render": "The network name is {internet_access:ssid}" }, + "just_created": { + "mappings": { + "0": { + "then": "You just created this element! Thanks for sharing this info with the world and helping people worldwide." + } + } + }, "last_edit": { "render": { "special": { diff --git a/langs/shared-questions/fr.json b/langs/shared-questions/fr.json index 393ddebd7e..fb9fac6e09 100644 --- a/langs/shared-questions/fr.json +++ b/langs/shared-questions/fr.json @@ -131,6 +131,13 @@ "question": "Quel est le nom du réseau pour l'accès Internet sans fil ?", "render": "Le nom du réseau est {internet_access:ssid}" }, + "just_created": { + "mappings": { + "0": { + "then": "Vous venez de créer cet élément ! Merci d'avoir partagé cette information avec le monde et d'aider les autres personnes." + } + } + }, "level": { "mappings": { "0": { diff --git a/langs/shared-questions/nl.json b/langs/shared-questions/nl.json index a1a66199ad..fe4e3ddd6e 100644 --- a/langs/shared-questions/nl.json +++ b/langs/shared-questions/nl.json @@ -131,6 +131,13 @@ "question": "Wat is de netwerknaam voor de draadloze internettoegang?", "render": "De netwerknaam is {internet_access:ssid}" }, + "just_created": { + "mappings": { + "0": { + "then": "Je hebt dit punt net toegevoegd! Bedankt om deze info met iedereen te delen en om de mensen wereldwijd te helpen." + } + } + }, "last_edit": { "render": { "special": { diff --git a/test.ts b/test.ts index b54dbd3aa2..ddca5d1741 100644 --- a/test.ts +++ b/test.ts @@ -9,10 +9,12 @@ import { UIEventSource } from "./Logic/UIEventSource" import { VariableUiElement } from "./UI/Base/VariableUIElement" import { FixedUiElement } from "./UI/Base/FixedUiElement" import Title from "./UI/Base/Title" -import WaySplitMap from "./UI/BigComponents/WaySplitMap.svelte" +import { WikipediaBoxOptions } from "./UI/Wikipedia/WikipediaBoxOptions" +import Wikipedia from "./Logic/Web/Wikipedia" +import WikipediaPanel from "./UI/Wikipedia/WikipediaPanel.svelte" import SvelteUIElement from "./UI/Base/SvelteUIElement" -import { OsmObject } from "./Logic/Osm/OsmObject" -import SplitRoadWizard from "./UI/Popup/SplitRoadWizard" +import LanguagePicker from "./UI/LanguagePicker" +import { Utils } from "./Utils" function testspecial() { const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) @@ -47,7 +49,12 @@ function testinput() { } async function testWaySplit() { - new SplitRoadWizard("way/28717919", {}).SetClass("w-full h-full").AttachTo("maindiv") + const ids = new UIEventSource(["Q42", "Q1"]) + new SvelteUIElement(WikipediaPanel, { wikiIds: ids, addEntry: true }).AttachTo("maindiv") + new LanguagePicker(["en", "nl"]).AttachTo("extradiv") + await Utils.waitFor(5000) + ids.data.push("Q430") + ids.ping() } testWaySplit().then((_) => console.log("inited")) //testinput() From 498b2bb8d6c4a6b3d27e4c15ab3ff5448dc5c00a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 21 Apr 2023 16:19:09 +0200 Subject: [PATCH 055/257] Chore: update schema files --- .../DenominationConfigJson.schema.json | 12 +- Docs/Schemas/DenominationConfigJsonJSC.ts | 9 +- Docs/Schemas/LayerConfigJson.schema.json | 507 ++++++-- Docs/Schemas/LayerConfigJsonJSC.ts | 504 ++++++-- Docs/Schemas/LayoutConfigJson.schema.json | 1087 ++++++++++++++--- Docs/Schemas/LayoutConfigJsonJSC.ts | 1083 +++++++++++++--- .../LineRenderingConfigJson.schema.json | 102 +- Docs/Schemas/LineRenderingConfigJsonJSC.ts | 99 +- Docs/Schemas/MappingConfigJson.schema.json | 102 +- Docs/Schemas/MappingConfigJsonJSC.ts | 99 +- Docs/Schemas/MoveConfigJson.schema.json | 12 +- Docs/Schemas/MoveConfigJsonJSC.ts | 9 +- .../PointRenderingConfigJson.schema.json | 160 ++- Docs/Schemas/PointRenderingConfigJsonJSC.ts | 157 ++- ...tionableTagRenderingConfigJson.schema.json | 184 ++- .../QuestionableTagRenderingConfigJsonJSC.ts | 181 ++- Docs/Schemas/RewritableConfigJson.schema.json | 102 +- Docs/Schemas/RewritableConfigJsonJSC.ts | 99 +- .../TagRenderingConfigJson.schema.json | 98 +- Docs/Schemas/TagRenderingConfigJsonJSC.ts | 96 +- Docs/Schemas/UnitConfigJson.schema.json | 12 +- Docs/Schemas/UnitConfigJsonJSC.ts | 9 +- 22 files changed, 3961 insertions(+), 762 deletions(-) diff --git a/Docs/Schemas/DenominationConfigJson.schema.json b/Docs/Schemas/DenominationConfigJson.schema.json index a24185884d..cfde140100 100644 --- a/Docs/Schemas/DenominationConfigJson.schema.json +++ b/Docs/Schemas/DenominationConfigJson.schema.json @@ -59,6 +59,14 @@ ], "additionalProperties": false }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "Record>": { + "type": "object", + "additionalProperties": false + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -122,10 +130,6 @@ "canonicalDenomination" ], "additionalProperties": false - }, - "Record": { - "type": "object", - "additionalProperties": false } }, "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/Docs/Schemas/DenominationConfigJsonJSC.ts b/Docs/Schemas/DenominationConfigJsonJSC.ts index 34b627f58a..fd7b2494f3 100644 --- a/Docs/Schemas/DenominationConfigJsonJSC.ts +++ b/Docs/Schemas/DenominationConfigJsonJSC.ts @@ -57,6 +57,12 @@ export default { "or" ] }, + "Record": { + "type": "object" + }, + "Record>": { + "type": "object" + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -119,9 +125,6 @@ export default { "required": [ "canonicalDenomination" ] - }, - "Record": { - "type": "object" } }, "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/Docs/Schemas/LayerConfigJson.schema.json b/Docs/Schemas/LayerConfigJson.schema.json index 4f052337cf..e7eb813fc4 100644 --- a/Docs/Schemas/LayerConfigJson.schema.json +++ b/Docs/Schemas/LayerConfigJson.schema.json @@ -7,90 +7,80 @@ "type": "string" }, "name": { - "description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control" - }, - "description": { - "description": "A description for this layer.\nShown in the layer selections and in the personel theme" - }, - "source": { - "description": "This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.\n\nIf no 'geojson' is defined, data will be fetched from overpass and the OSM-API.\n\nEvery source _must_ define which tags _must_ be present in order to be picked up.", + "description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control", "anyOf": [ { - "allOf": [ - { - "type": "object", - "properties": { - "osmTags": { - "$ref": "#/definitions/TagConfigJson", - "description": "Every source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", - "type": "number" - } - }, - "required": [ - "osmTags" - ] + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "description": { + "description": "A description for this layer.\nShown in the layer selections and in the personel theme", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "source": { + "description": "This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.\n\nIf no 'geojson' is defined, data will be fetched from overpass and the OSM-API.\n\nEvery source _must_ define which tags _must_ be present in order to be picked up.\n\nNote: a source must always be defined. 'special' is only allowed if this is a builtin-layer", + "anyOf": [ + { + "type": "object", + "properties": { + "osmTags": { + "$ref": "#/definitions/TagConfigJson", + "description": "Every source must set which tags have to be present in order to load the given layer." }, - { - "type": "object", - "properties": { - "overpassScript": { - "description": "If set, this custom overpass-script will be used instead of building one by using the OSM-tags.\nSpecifying OSM-tags is still obligatory and will still hide non-matching items and they will be used for the rest of the pipeline.\n_This should be really rare_.\n\nFor example, when you want to fetch all grass-areas in parks and which are marked as publicly accessible:\n```\n\"source\": {\n \"overpassScript\":\n \"way[\\\"leisure\\\"=\\\"park\\\"];node(w);is_in;area._[\\\"leisure\\\"=\\\"park\\\"];(way(area)[\\\"landuse\\\"=\\\"grass\\\"]; node(w); );\",\n \"osmTags\": \"access=yes\"\n}\n```", - "type": "string" - } - } + "maxCacheAge": { + "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", + "type": "number" } + }, + "required": [ + "osmTags" ] }, { - "allOf": [ - { - "type": "object", - "properties": { - "osmTags": { - "$ref": "#/definitions/TagConfigJson", - "description": "Every source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", - "type": "number" - } - }, - "required": [ - "osmTags" - ] + "type": "object", + "properties": { + "geoJson": { + "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}", + "type": "string" }, - { - "type": "object", - "properties": { - "geoJson": { - "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}", - "type": "string" - }, - "geoJsonZoomLevel": { - "description": "To load a tiled geojson layer, set the zoomlevel of the tiles", - "type": "number" - }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache", - "type": "boolean" - }, - "mercatorCrs": { - "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this", - "type": "boolean" - }, - "idKey": { - "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'", - "type": "string" - } - }, - "required": [ - "geoJson" - ] + "geoJsonZoomLevel": { + "description": "To load a tiled geojson layer, set the zoomlevel of the tiles", + "type": "number" + }, + "isOsmCache": { + "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache", + "type": "boolean" + }, + "mercatorCrs": { + "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this", + "type": "boolean" + }, + "idKey": { + "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'", + "type": "string" } + }, + "required": [ + "geoJson" ] + }, + { + "enum": [ + "special", + "special:library" + ], + "type": "string" } ] }, @@ -214,7 +204,15 @@ "type": "object", "properties": { "title": { - "description": "The title - shown on the 'add-new'-button.\n\nThis should include the article of the noun, e.g. 'a hydrant', 'a bicycle pump'.\nThis text will be inserted into `Add {category} here`, becoming `Add a hydrant here`.\n\nDo _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!" + "description": "The title - shown on the 'add-new'-button.\n\nThis should include the article of the noun, e.g. 'a hydrant', 'a bicycle pump'.\nThis text will be inserted into `Add {category} here`, becoming `Add a hydrant here`.\n\nDo _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "tags": { "description": "The tags to add. It determines the icon too", @@ -224,7 +222,15 @@ } }, "description": { - "description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)" + "description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "exampleImages": { "description": "Example images, which show real-life pictures of what such a feature might look like\n\nType: image", @@ -299,6 +305,9 @@ { "type": "object", "properties": { + "id": { + "type": "string" + }, "builtin": { "anyOf": [ { @@ -321,28 +330,6 @@ "override" ] }, - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "builtin": { - "type": "array", - "items": { - "type": "string" - } - }, - "override": { - "$ref": "#/definitions/Partial" - } - }, - "required": [ - "builtin", - "id", - "override" - ] - }, { "allOf": [ { @@ -507,6 +494,14 @@ ], "additionalProperties": false }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "Record>": { + "type": "object", + "additionalProperties": false + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -571,10 +566,6 @@ ], "additionalProperties": false }, - "Record": { - "type": "object", - "additionalProperties": false - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -583,10 +574,6 @@ "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -594,11 +581,67 @@ "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -616,6 +659,22 @@ } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -640,7 +699,15 @@ "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", @@ -796,7 +863,7 @@ "type": "object", "properties": { "location": { - "description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint.\n'projected_centerpoint' will show an item on the line itself, near the middle of the line. (LineStrings only)", + "description": "All the locations that this point should be rendered at.\nPossible values are:\n- `point`: only renders points at their location\n- `centroid`: show a symbol at the centerpoint of a (multi)Linestring and (multi)polygon. Points will _not_ be rendered with this\n- `projected_centerpoint`: Only on (multi)linestrings: calculate the centerpoint and snap it to the way\n- `start` and `end`: only on linestrings: add a point to the first/last coordinate of the LineString", "type": "array", "items": { "type": "string" @@ -875,7 +942,7 @@ ] }, "css": { - "description": "A snippet of css code", + "description": "A snippet of css code which is applied onto the container of the entire marker", "anyOf": [ { "$ref": "#/definitions/TagRenderingConfigJson" @@ -886,7 +953,7 @@ ] }, "cssClasses": { - "description": "A snippet of css-classes. They can be space-separated", + "description": "A snippet of css-classes which are applied onto the container of the entire marker. They can be space-separated", "anyOf": [ { "$ref": "#/definitions/TagRenderingConfigJson" @@ -895,6 +962,58 @@ "type": "string" } ] + }, + "labelCss": { + "description": "Css that is applied onto the label", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "labelCssClasses": { + "description": "Css classes that are applied onto the label; can be space-separated", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "pitchAlignment": { + "description": "If the map is pitched, the marker will stay parallel to the screen.\nSet to 'map' if you want to put it flattened on the map", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ] + }, + "rotationAlignment": { + "description": "If the map is rotated, the icon will still point to the north if no rotation was applied", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ] } }, "required": [ @@ -1101,7 +1220,7 @@ } }, "inline": { - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present.", "type": "boolean" }, "default": { @@ -1128,10 +1247,6 @@ "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -1139,11 +1254,67 @@ "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -1160,6 +1331,22 @@ "type": "string" } ] + }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] } }, "additionalProperties": false @@ -1216,7 +1403,7 @@ } }, "inline": { - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present.", "type": "boolean" }, "default": { @@ -1243,10 +1430,6 @@ "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -1254,11 +1437,67 @@ "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -1275,6 +1514,22 @@ "type": "string" } ] + }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] } }, "additionalProperties": false diff --git a/Docs/Schemas/LayerConfigJsonJSC.ts b/Docs/Schemas/LayerConfigJsonJSC.ts index b03cc5a5db..4eaf333efc 100644 --- a/Docs/Schemas/LayerConfigJsonJSC.ts +++ b/Docs/Schemas/LayerConfigJsonJSC.ts @@ -7,90 +7,80 @@ export default { "type": "string" }, "name": { - "description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control" - }, - "description": { - "description": "A description for this layer.\nShown in the layer selections and in the personel theme" - }, - "source": { - "description": "This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.\n\nIf no 'geojson' is defined, data will be fetched from overpass and the OSM-API.\n\nEvery source _must_ define which tags _must_ be present in order to be picked up.", + "description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control", "anyOf": [ { - "allOf": [ - { - "type": "object", - "properties": { - "osmTags": { - "$ref": "#/definitions/TagConfigJson", - "description": "Every source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", - "type": "number" - } - }, - "required": [ - "osmTags" - ] + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "description": { + "description": "A description for this layer.\nShown in the layer selections and in the personel theme", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "source": { + "description": "This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.\n\nIf no 'geojson' is defined, data will be fetched from overpass and the OSM-API.\n\nEvery source _must_ define which tags _must_ be present in order to be picked up.\n\nNote: a source must always be defined. 'special' is only allowed if this is a builtin-layer", + "anyOf": [ + { + "type": "object", + "properties": { + "osmTags": { + "$ref": "#/definitions/TagConfigJson", + "description": "Every source must set which tags have to be present in order to load the given layer." }, - { - "type": "object", - "properties": { - "overpassScript": { - "description": "If set, this custom overpass-script will be used instead of building one by using the OSM-tags.\nSpecifying OSM-tags is still obligatory and will still hide non-matching items and they will be used for the rest of the pipeline.\n_This should be really rare_.\n\nFor example, when you want to fetch all grass-areas in parks and which are marked as publicly accessible:\n```\n\"source\": {\n \"overpassScript\":\n \"way[\\\"leisure\\\"=\\\"park\\\"];node(w);is_in;area._[\\\"leisure\\\"=\\\"park\\\"];(way(area)[\\\"landuse\\\"=\\\"grass\\\"]; node(w); );\",\n \"osmTags\": \"access=yes\"\n}\n```", - "type": "string" - } - } + "maxCacheAge": { + "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", + "type": "number" } + }, + "required": [ + "osmTags" ] }, { - "allOf": [ - { - "type": "object", - "properties": { - "osmTags": { - "$ref": "#/definitions/TagConfigJson", - "description": "Every source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", - "type": "number" - } - }, - "required": [ - "osmTags" - ] + "type": "object", + "properties": { + "geoJson": { + "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}", + "type": "string" }, - { - "type": "object", - "properties": { - "geoJson": { - "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}", - "type": "string" - }, - "geoJsonZoomLevel": { - "description": "To load a tiled geojson layer, set the zoomlevel of the tiles", - "type": "number" - }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache", - "type": "boolean" - }, - "mercatorCrs": { - "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this", - "type": "boolean" - }, - "idKey": { - "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'", - "type": "string" - } - }, - "required": [ - "geoJson" - ] + "geoJsonZoomLevel": { + "description": "To load a tiled geojson layer, set the zoomlevel of the tiles", + "type": "number" + }, + "isOsmCache": { + "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache", + "type": "boolean" + }, + "mercatorCrs": { + "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this", + "type": "boolean" + }, + "idKey": { + "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'", + "type": "string" } + }, + "required": [ + "geoJson" ] + }, + { + "enum": [ + "special", + "special:library" + ], + "type": "string" } ] }, @@ -214,7 +204,15 @@ export default { "type": "object", "properties": { "title": { - "description": "The title - shown on the 'add-new'-button.\n\nThis should include the article of the noun, e.g. 'a hydrant', 'a bicycle pump'.\nThis text will be inserted into `Add {category} here`, becoming `Add a hydrant here`.\n\nDo _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!" + "description": "The title - shown on the 'add-new'-button.\n\nThis should include the article of the noun, e.g. 'a hydrant', 'a bicycle pump'.\nThis text will be inserted into `Add {category} here`, becoming `Add a hydrant here`.\n\nDo _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "tags": { "description": "The tags to add. It determines the icon too", @@ -224,7 +222,15 @@ export default { } }, "description": { - "description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)" + "description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "exampleImages": { "description": "Example images, which show real-life pictures of what such a feature might look like\n\nType: image", @@ -299,6 +305,9 @@ export default { { "type": "object", "properties": { + "id": { + "type": "string" + }, "builtin": { "anyOf": [ { @@ -321,28 +330,6 @@ export default { "override" ] }, - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "builtin": { - "type": "array", - "items": { - "type": "string" - } - }, - "override": { - "$ref": "#/definitions/Partial" - } - }, - "required": [ - "builtin", - "id", - "override" - ] - }, { "allOf": [ { @@ -505,6 +492,12 @@ export default { "or" ] }, + "Record": { + "type": "object" + }, + "Record>": { + "type": "object" + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -568,9 +561,6 @@ export default { "canonicalDenomination" ] }, - "Record": { - "type": "object" - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -579,10 +569,6 @@ export default { "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -590,11 +576,67 @@ export default { "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -612,6 +654,22 @@ export default { } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -636,7 +694,15 @@ export default { "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", @@ -788,7 +854,7 @@ export default { "type": "object", "properties": { "location": { - "description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint.\n'projected_centerpoint' will show an item on the line itself, near the middle of the line. (LineStrings only)", + "description": "All the locations that this point should be rendered at.\nPossible values are:\n- `point`: only renders points at their location\n- `centroid`: show a symbol at the centerpoint of a (multi)Linestring and (multi)polygon. Points will _not_ be rendered with this\n- `projected_centerpoint`: Only on (multi)linestrings: calculate the centerpoint and snap it to the way\n- `start` and `end`: only on linestrings: add a point to the first/last coordinate of the LineString", "type": "array", "items": { "type": "string" @@ -867,7 +933,7 @@ export default { ] }, "css": { - "description": "A snippet of css code", + "description": "A snippet of css code which is applied onto the container of the entire marker", "anyOf": [ { "$ref": "#/definitions/TagRenderingConfigJson" @@ -878,7 +944,7 @@ export default { ] }, "cssClasses": { - "description": "A snippet of css-classes. They can be space-separated", + "description": "A snippet of css-classes which are applied onto the container of the entire marker. They can be space-separated", "anyOf": [ { "$ref": "#/definitions/TagRenderingConfigJson" @@ -887,6 +953,58 @@ export default { "type": "string" } ] + }, + "labelCss": { + "description": "Css that is applied onto the label", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "labelCssClasses": { + "description": "Css classes that are applied onto the label; can be space-separated", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "pitchAlignment": { + "description": "If the map is pitched, the marker will stay parallel to the screen.\nSet to 'map' if you want to put it flattened on the map", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ] + }, + "rotationAlignment": { + "description": "If the map is rotated, the icon will still point to the north if no rotation was applied", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ] } }, "required": [ @@ -1090,7 +1208,7 @@ export default { } }, "inline": { - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present.", "type": "boolean" }, "default": { @@ -1117,10 +1235,6 @@ export default { "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -1128,11 +1242,67 @@ export default { "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -1149,6 +1319,22 @@ export default { "type": "string" } ] + }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] } } }, @@ -1204,7 +1390,7 @@ export default { } }, "inline": { - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present.", "type": "boolean" }, "default": { @@ -1231,10 +1417,6 @@ export default { "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -1242,11 +1424,67 @@ export default { "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -1263,6 +1501,22 @@ export default { "type": "string" } ] + }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] } } }, diff --git a/Docs/Schemas/LayoutConfigJson.schema.json b/Docs/Schemas/LayoutConfigJson.schema.json index 76889c9419..c004dda312 100644 --- a/Docs/Schemas/LayoutConfigJson.schema.json +++ b/Docs/Schemas/LayoutConfigJson.schema.json @@ -18,16 +18,48 @@ } }, "title": { - "description": "The title, as shown in the welcome message and the more-screen." + "description": "The title, as shown in the welcome message and the more-screen.", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "shortDescription": { - "description": "A short description, showed as social description and in the 'more theme'-buttons.\nNote that if this one is not defined, the first sentence of 'description' is used" + "description": "A short description, showed as social description and in the 'more theme'-buttons.\nNote that if this one is not defined, the first sentence of 'description' is used", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "description": { - "description": "The description, as shown in the welcome message and the more-screen" + "description": "The description, as shown in the welcome message and the more-screen", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "descriptionTail": { - "description": "A part of the description, shown under the login-button." + "description": "A part of the description, shown under the login-button.", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "The icon representing this theme.\nUsed as logo in the more-screen and (for official themes) as favicon, webmanifest logo, ...\nEither a URL or a base64 encoded value (which should include 'data:image/svg+xml;base64)\n\nType: icon", @@ -71,7 +103,19 @@ "description": "Define some (overlay) slippy map tilesources", "type": "array", "items": { - "$ref": "#/definitions/default_6" + "allOf": [ + { + "$ref": "#/definitions/RasterLayerProperties" + }, + { + "type": "object", + "properties": { + "defaultState": { + "type": "boolean" + } + } + } + ] } }, "layers": { @@ -98,7 +142,9 @@ } ] }, - "override": {}, + "override": { + "$ref": "#/definitions/Partial" + }, "hideTagRenderingsWithLabels": { "description": "TagRenderings with any of these labels will be removed from the layer.\nNote that the 'id' and 'group' are considered labels too", "type": "array", @@ -118,30 +164,6 @@ ] } }, - "clustering": { - "description": "If defined, data will be clustered.\nDefaults to {maxZoom: 16, minNeeded: 500}", - "anyOf": [ - { - "type": "object", - "properties": { - "maxZoom": { - "description": "All zoom levels above 'maxzoom' are not clustered anymore.\nDefaults to 18", - "type": "number" - }, - "minNeededElements": { - "description": "The number of elements per tile needed to start clustering\nIf clustering is defined, defaults to 250", - "type": "number" - } - } - }, - { - "enum": [ - false - ], - "type": "boolean" - } - ] - }, "customCss": { "description": "The URL of a custom CSS stylesheet to modify the layout", "type": "string" @@ -266,6 +288,10 @@ "overpassTimeout": { "description": "Set a different timeout for overpass queries - in seconds. Default: 30s", "type": "number" + }, + "enableNodeDatabase": { + "description": "Enables tracking of all nodes when data is loaded.\nThis is useful for the 'ImportWay' and 'ConflateWay'-buttons who need this database.\n\nNote: this flag will be automatically set.", + "type": "boolean" } }, "required": [ @@ -337,6 +363,14 @@ ], "additionalProperties": false }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "Record>": { + "type": "object", + "additionalProperties": false + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -401,10 +435,6 @@ ], "additionalProperties": false }, - "Record": { - "type": "object", - "additionalProperties": false - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -413,10 +443,6 @@ "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -424,11 +450,67 @@ "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -446,6 +528,22 @@ } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -470,7 +568,15 @@ "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", @@ -626,7 +732,7 @@ "type": "object", "properties": { "location": { - "description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint.\n'projected_centerpoint' will show an item on the line itself, near the middle of the line. (LineStrings only)", + "description": "All the locations that this point should be rendered at.\nPossible values are:\n- `point`: only renders points at their location\n- `centroid`: show a symbol at the centerpoint of a (multi)Linestring and (multi)polygon. Points will _not_ be rendered with this\n- `projected_centerpoint`: Only on (multi)linestrings: calculate the centerpoint and snap it to the way\n- `start` and `end`: only on linestrings: add a point to the first/last coordinate of the LineString", "type": "array", "items": { "type": "string" @@ -705,7 +811,7 @@ ] }, "css": { - "description": "A snippet of css code", + "description": "A snippet of css code which is applied onto the container of the entire marker", "anyOf": [ { "$ref": "#/definitions/TagRenderingConfigJson" @@ -716,7 +822,7 @@ ] }, "cssClasses": { - "description": "A snippet of css-classes. They can be space-separated", + "description": "A snippet of css-classes which are applied onto the container of the entire marker. They can be space-separated", "anyOf": [ { "$ref": "#/definitions/TagRenderingConfigJson" @@ -725,6 +831,58 @@ "type": "string" } ] + }, + "labelCss": { + "description": "Css that is applied onto the label", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "labelCssClasses": { + "description": "Css classes that are applied onto the label; can be space-separated", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "pitchAlignment": { + "description": "If the map is pitched, the marker will stay parallel to the screen.\nSet to 'map' if you want to put it flattened on the map", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ] + }, + "rotationAlignment": { + "description": "If the map is rotated, the icon will still point to the north if no rotation was applied", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ] } }, "required": [ @@ -931,7 +1089,7 @@ } }, "inline": { - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present.", "type": "boolean" }, "default": { @@ -958,10 +1116,6 @@ "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -969,11 +1123,67 @@ "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -990,6 +1200,22 @@ "type": "string" } ] + }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] } }, "additionalProperties": false @@ -1046,7 +1272,7 @@ } }, "inline": { - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present.", "type": "boolean" }, "default": { @@ -1073,10 +1299,6 @@ "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -1084,11 +1306,67 @@ "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -1105,6 +1383,22 @@ "type": "string" } ] + }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] } }, "additionalProperties": false @@ -1359,42 +1653,63 @@ "type": "object", "additionalProperties": false }, - "default_6": { - "description": "Configuration for a tilesource config", + "RasterLayerProperties": { "type": "object", "properties": { - "id": { - "description": "Id of this overlay, used in the URL-parameters to set the state", - "type": "string" - }, - "source": { - "description": "The path, where {x}, {y} and {z} will be substituted", - "type": "string" + "name": { + "description": "The name of the imagery source", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "isOverlay": { - "description": "Wether or not this is an overlay. Default: true", "type": "boolean" }, - "name": { - "description": "How this will be shown in the selection menu.\nMake undefined if this may not be toggled" + "id": { + "type": "string" }, - "minZoom": { - "description": "Only visible at this or a higher zoom level", + "url": { + "type": "string" + }, + "category": { + "type": "string" + }, + "attribution": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "text": { + "type": "string" + }, + "html": { + "type": "string" + }, + "required": { + "type": "boolean" + } + } + }, + "min_zoom": { "type": "number" }, - "maxZoom": { - "description": "Only visible at this or a lower zoom level", + "max_zoom": { "type": "number" }, - "defaultState": { - "description": "The default state, set to false to hide by default", + "best": { "type": "boolean" } }, "required": [ - "defaultState", "id", - "source" + "name", + "url" ], "additionalProperties": false }, @@ -1407,90 +1722,80 @@ "type": "string" }, "name": { - "description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control" - }, - "description": { - "description": "A description for this layer.\nShown in the layer selections and in the personel theme" - }, - "source": { - "description": "This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.\n\nIf no 'geojson' is defined, data will be fetched from overpass and the OSM-API.\n\nEvery source _must_ define which tags _must_ be present in order to be picked up.", + "description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control", "anyOf": [ { - "allOf": [ - { - "type": "object", - "properties": { - "osmTags": { - "$ref": "#/definitions/TagConfigJson", - "description": "Every source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", - "type": "number" - } - }, - "required": [ - "osmTags" - ] + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "description": { + "description": "A description for this layer.\nShown in the layer selections and in the personel theme", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "source": { + "description": "This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.\n\nIf no 'geojson' is defined, data will be fetched from overpass and the OSM-API.\n\nEvery source _must_ define which tags _must_ be present in order to be picked up.\n\nNote: a source must always be defined. 'special' is only allowed if this is a builtin-layer", + "anyOf": [ + { + "type": "object", + "properties": { + "osmTags": { + "$ref": "#/definitions/TagConfigJson", + "description": "Every source must set which tags have to be present in order to load the given layer." }, - { - "type": "object", - "properties": { - "overpassScript": { - "description": "If set, this custom overpass-script will be used instead of building one by using the OSM-tags.\nSpecifying OSM-tags is still obligatory and will still hide non-matching items and they will be used for the rest of the pipeline.\n_This should be really rare_.\n\nFor example, when you want to fetch all grass-areas in parks and which are marked as publicly accessible:\n```\n\"source\": {\n \"overpassScript\":\n \"way[\\\"leisure\\\"=\\\"park\\\"];node(w);is_in;area._[\\\"leisure\\\"=\\\"park\\\"];(way(area)[\\\"landuse\\\"=\\\"grass\\\"]; node(w); );\",\n \"osmTags\": \"access=yes\"\n}\n```", - "type": "string" - } - } + "maxCacheAge": { + "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", + "type": "number" } + }, + "required": [ + "osmTags" ] }, { - "allOf": [ - { - "type": "object", - "properties": { - "osmTags": { - "$ref": "#/definitions/TagConfigJson", - "description": "Every source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", - "type": "number" - } - }, - "required": [ - "osmTags" - ] + "type": "object", + "properties": { + "geoJson": { + "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}", + "type": "string" }, - { - "type": "object", - "properties": { - "geoJson": { - "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}", - "type": "string" - }, - "geoJsonZoomLevel": { - "description": "To load a tiled geojson layer, set the zoomlevel of the tiles", - "type": "number" - }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache", - "type": "boolean" - }, - "mercatorCrs": { - "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this", - "type": "boolean" - }, - "idKey": { - "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'", - "type": "string" - } - }, - "required": [ - "geoJson" - ] + "geoJsonZoomLevel": { + "description": "To load a tiled geojson layer, set the zoomlevel of the tiles", + "type": "number" + }, + "isOsmCache": { + "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache", + "type": "boolean" + }, + "mercatorCrs": { + "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this", + "type": "boolean" + }, + "idKey": { + "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'", + "type": "string" } + }, + "required": [ + "geoJson" ] + }, + { + "enum": [ + "special", + "special:library" + ], + "type": "string" } ] }, @@ -1614,7 +1919,15 @@ "type": "object", "properties": { "title": { - "description": "The title - shown on the 'add-new'-button.\n\nThis should include the article of the noun, e.g. 'a hydrant', 'a bicycle pump'.\nThis text will be inserted into `Add {category} here`, becoming `Add a hydrant here`.\n\nDo _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!" + "description": "The title - shown on the 'add-new'-button.\n\nThis should include the article of the noun, e.g. 'a hydrant', 'a bicycle pump'.\nThis text will be inserted into `Add {category} here`, becoming `Add a hydrant here`.\n\nDo _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "tags": { "description": "The tags to add. It determines the icon too", @@ -1624,7 +1937,15 @@ } }, "description": { - "description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)" + "description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "exampleImages": { "description": "Example images, which show real-life pictures of what such a feature might look like\n\nType: image", @@ -1699,6 +2020,9 @@ { "type": "object", "properties": { + "id": { + "type": "string" + }, "builtin": { "anyOf": [ { @@ -1721,28 +2045,6 @@ "override" ] }, - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "builtin": { - "type": "array", - "items": { - "type": "string" - } - }, - "override": { - "$ref": "#/definitions/Partial" - } - }, - "required": [ - "builtin", - "id", - "override" - ] - }, { "allOf": [ { @@ -1850,6 +2152,439 @@ ], "additionalProperties": false }, + "Partial": { + "type": "object", + "properties": { + "id": { + "description": "The id of this layer.\nThis should be a simple, lowercase, human readable string that is used to identify the layer.", + "type": "string" + }, + "name": { + "description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "description": { + "description": "A description for this layer.\nShown in the layer selections and in the personel theme", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "source": { + "description": "This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.\n\nIf no 'geojson' is defined, data will be fetched from overpass and the OSM-API.\n\nEvery source _must_ define which tags _must_ be present in order to be picked up.\n\nNote: a source must always be defined. 'special' is only allowed if this is a builtin-layer", + "anyOf": [ + { + "type": "object", + "properties": { + "osmTags": { + "$ref": "#/definitions/TagConfigJson", + "description": "Every source must set which tags have to be present in order to load the given layer." + }, + "maxCacheAge": { + "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", + "type": "number" + } + }, + "required": [ + "osmTags" + ] + }, + { + "type": "object", + "properties": { + "geoJson": { + "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}", + "type": "string" + }, + "geoJsonZoomLevel": { + "description": "To load a tiled geojson layer, set the zoomlevel of the tiles", + "type": "number" + }, + "isOsmCache": { + "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache", + "type": "boolean" + }, + "mercatorCrs": { + "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this", + "type": "boolean" + }, + "idKey": { + "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'", + "type": "string" + } + }, + "required": [ + "geoJson" + ] + }, + { + "enum": [ + "special", + "special:library" + ], + "type": "string" + } + ] + }, + "calculatedTags": { + "description": "A list of extra tags to calculate, specified as \"keyToAssignTo=javascript-expression\".\nThere are a few extra functions available. Refer to Docs/CalculatedTags.md for more information\nThe functions will be run in order, e.g.\n[\n \"_max_overlap_m2=Math.max(...feat.overlapsWith(\"someOtherLayer\").map(o => o.overlap))\n \"_max_overlap_ratio=Number(feat._max_overlap_m2)/feat.area\n]\n\nThe specified tags are evaluated lazily. E.g. if a calculated tag is only used in the popup (e.g. the number of nearby features),\nthe expensive calculation will only be performed then for that feature. This avoids clogging up the contributors PC when all features are loaded.\n\nIf a tag has to be evaluated strictly, use ':=' instead:\n\n[\n\"_some_key:=some_javascript_expression\"\n]", + "type": "array", + "items": { + "type": "string" + } + }, + "doNotDownload": { + "description": "If set, this layer will not query overpass; but it'll still match the tags above which are by chance returned by other layers.\nWorks well together with 'passAllFeatures', to add decoration", + "type": "boolean" + }, + "isShown": { + "description": "If set, only features matching this extra tag will be shown.\nThis is useful to hide certain features from view.\n\nImportant: hiding features does not work dynamically, but is only calculated when the data is first renders.\nThis implies that it is not possible to hide a feature after a tagging change\n\nThe default value is 'yes'", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, + "forceLoad": { + "description": "Advanced option - might be set by the theme compiler\n\nIf true, this data will _always_ be loaded, even if the theme is disabled", + "type": "boolean" + }, + "minzoom": { + "description": "The minimum needed zoomlevel required before loading of the data start\nDefault: 0", + "type": "number" + }, + "shownByDefault": { + "description": "Indicates if this layer is shown by default;\ncan be used to hide a layer from start, or to load the layer but only to show it where appropriate (e.g. for snapping to it)", + "type": "boolean" + }, + "minzoomVisible": { + "description": "The zoom level at which point the data is hidden again\nDefault: 100 (thus: always visible", + "type": "number" + }, + "title": { + "description": "The title shown in a popup for elements of this layer.", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "titleIcons": { + "description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\n\nType: icon[]", + "anyOf": [ + { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + } + }, + { + "type": "array", + "items": [ + { + "type": "string", + "enum": [ + "defaults" + ] + } + ], + "minItems": 1, + "maxItems": 1 + } + ] + }, + "mapRendering": { + "description": "Visualisation of the items on the map", + "anyOf": [ + { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/default_4" + }, + { + "$ref": "#/definitions/default_5" + }, + { + "$ref": "#/definitions/default" + } + ] + } + }, + { + "type": "null" + } + ] + }, + "passAllFeatures": { + "description": "If set, this layer will pass all the features it receives onto the next layer.\nThis is ideal for decoration, e.g. directionss on cameras", + "type": "boolean" + }, + "presets": { + "description": "Presets for this layer.\nA preset shows up when clicking the map on a without data (or when right-clicking/long-pressing);\nit will prompt the user to add a new point.\n\nThe most important aspect are the tags, which define which tags the new point will have;\nThe title is shown in the dialog, along with the first sentence of the description.\n\nUpon confirmation, the full description is shown beneath the buttons - perfect to add pictures and examples.\n\nNote: the icon of the preset is determined automatically based on the tags and the icon above. Don't worry about that!\nNB: if no presets are defined, the popup to add new points doesn't show up at all", + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "description": "The title - shown on the 'add-new'-button.\n\nThis should include the article of the noun, e.g. 'a hydrant', 'a bicycle pump'.\nThis text will be inserted into `Add {category} here`, becoming `Add a hydrant here`.\n\nDo _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "tags": { + "description": "The tags to add. It determines the icon too", + "type": "array", + "items": { + "type": "string" + } + }, + "description": { + "description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "exampleImages": { + "description": "Example images, which show real-life pictures of what such a feature might look like\n\nType: image", + "type": "array", + "items": { + "type": "string" + } + }, + "preciseInput": { + "description": "If set, the user will prompted to confirm the location before actually adding the data.\nThis will be with a 'drag crosshair'-method.\n\nIf 'preferredBackgroundCategory' is set, the element will attempt to pick a background layer of that category.", + "anyOf": [ + { + "type": "object", + "properties": { + "preferredBackground": { + "description": "The type of background picture", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "snapToLayer": { + "description": "If specified, these layers will be shown to and the new point will be snapped towards it", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "maxSnapDistance": { + "description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10", + "type": "number" + } + } + }, + { + "enum": [ + true + ], + "type": "boolean" + } + ] + } + }, + "required": [ + "tags", + "title" + ] + } + }, + "tagRenderings": { + "description": "All the tag renderings.\nA tag rendering is a block that either shows the known value or asks a question.\n\nRefer to the class `TagRenderingConfigJson` to see the possibilities.\n\nNote that we can also use a string here - where the string refers to a tag rendering defined in `assets/questions/questions.json`,\nwhere a few very general questions are defined e.g. website, phone number, ...\nFurthermore, _all_ the questions of another layer can be reused with `otherlayer.*`\nIf you need only a single of the tagRenderings, use `otherlayer.tagrenderingId`\nIf one or more questions have a 'group' or 'label' set, select all the entries with the corresponding group or label with `otherlayer.*group`\nRemark: if a tagRendering is 'lent' from another layer, the 'source'-tags are copied and added as condition.\nIf they are not wanted, remove them with an override\n\nA special value is 'questions', which indicates the location of the questions box. If not specified, it'll be appended to the bottom of the featureInfobox.\n\nAt last, one can define a group of renderings where parts of all strings will be replaced by multiple other strings.\nThis is mainly create questions for a 'left' and a 'right' side of the road.\nThese will be grouped and questions will be asked together", + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/QuestionableTagRenderingConfigJson" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "builtin": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "override": { + "$ref": "#/definitions/Partial" + } + }, + "required": [ + "builtin", + "override" + ] + }, + { + "allOf": [ + { + "$ref": "#/definitions/default<(string|QuestionableTagRenderingConfigJson|{builtin:string;override:Partial;})[]>" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + ] + }, + { + "type": "string" + } + ] + } + }, + "filter": { + "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in 'filters.json' for the appropriate filter or will try to parse it as `layername.filterid` and us that one", + "anyOf": [ + { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/default_1" + }, + { + "type": "string" + } + ] + } + }, + { + "type": "object", + "properties": { + "sameAs": { + "type": "string" + } + }, + "required": [ + "sameAs" + ] + } + ] + }, + "deletion": { + "description": "This block defines under what circumstances the delete dialog is shown for objects of this layer.\nIf set, a dialog is shown to the user to (soft) delete the point.\nThe dialog is built to be user friendly and to prevent mistakes.\nIf deletion is not possible, the dialog will hide itself and show the reason of non-deletability instead.\n\nTo configure, the following values are possible:\n\n- false: never ever show the delete button\n- true: show the default delete button\n- undefined: use the mapcomplete default to show deletion or not. Currently, this is the same as 'false' but this will change in the future\n- or: a hash with options (see below)\n\n The delete dialog\n =================\n\n\n\n#### Hard deletion if enough experience\n\nA feature can only be deleted from OpenStreetMap by mapcomplete if:\n\n- It is a node\n- No ways or relations use the node\n- The logged-in user has enough experience OR the user is the only one to have edited the point previously\n- The logged-in user has no unread messages (or has a ton of experience)\n- The user did not select one of the 'non-delete-options' (see below)\n\nIn all other cases, a 'soft deletion' is used.\n\n#### Soft deletion\n\nA 'soft deletion' is when the point isn't deleted from OSM but retagged so that it'll won't how up in the mapcomplete theme anymore.\nThis makes it look like it was deleted, without doing damage. A fixme will be added to the point.\n\nNote that a soft deletion is _only_ possible if these tags are provided by the theme creator, as they'll be different for every theme\n\n#### No-delete options\n\nIn some cases, the contributor might want to delete something for the wrong reason (e.g. someone who wants to have a path removed \"because the path is on their private property\").\nHowever, the path exists in reality and should thus be on OSM - otherwise the next contributor will pass by and notice \"hey, there is a path missing here! Let me redraw it in OSM!)\n\nThe correct approach is to retag the feature in such a way that it is semantically correct *and* that it doesn't show up on the theme anymore.\nA no-delete option is offered as 'reason to delete it', but secretly retags.", + "anyOf": [ + { + "$ref": "#/definitions/DeleteConfigJson" + }, + { + "type": "boolean" + } + ] + }, + "allowMove": { + "description": "Indicates if a point can be moved and configures the modalities.\n\nA feature can be moved by MapComplete if:\n\n- It is a point\n- The point is _not_ part of a way or a a relation.\n\nOff by default. Can be enabled by setting this flag or by configuring.", + "anyOf": [ + { + "$ref": "#/definitions/default_3" + }, + { + "type": "boolean" + } + ] + }, + "allowSplit": { + "description": "If set, a 'split this way' button is shown on objects rendered as LineStrings, e.g. highways.\n\nIf the way is part of a relation, MapComplete will attempt to update this relation as well", + "type": "boolean" + }, + "units": { + "type": "array", + "items": { + "$ref": "#/definitions/default_2" + } + }, + "syncSelection": { + "description": "If set, synchronizes whether or not this layer is enabled.\n\nno: Do not sync at all, always revert to default\nlocal: keep selection on local storage\ntheme-only: sync via OSM, but this layer will only be toggled in this theme\nglobal: all layers with this ID will be synced accross all themes", + "enum": [ + "global", + "local", + "no", + "theme-only" + ], + "type": "string" + }, + "#": { + "description": "Used for comments and/or to disable some checks\n\nno-question-hint-check: disables a check in MiscTagRenderingChecks which complains about 'div', 'span' or 'class=subtle'-HTML elements in the tagRendering", + "type": "string" + } + }, + "additionalProperties": false + }, "default": { "type": "object", "properties": { diff --git a/Docs/Schemas/LayoutConfigJsonJSC.ts b/Docs/Schemas/LayoutConfigJsonJSC.ts index 2172da51d9..7c396ce1e4 100644 --- a/Docs/Schemas/LayoutConfigJsonJSC.ts +++ b/Docs/Schemas/LayoutConfigJsonJSC.ts @@ -18,16 +18,48 @@ export default { } }, "title": { - "description": "The title, as shown in the welcome message and the more-screen." + "description": "The title, as shown in the welcome message and the more-screen.", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "shortDescription": { - "description": "A short description, showed as social description and in the 'more theme'-buttons.\nNote that if this one is not defined, the first sentence of 'description' is used" + "description": "A short description, showed as social description and in the 'more theme'-buttons.\nNote that if this one is not defined, the first sentence of 'description' is used", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "description": { - "description": "The description, as shown in the welcome message and the more-screen" + "description": "The description, as shown in the welcome message and the more-screen", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "descriptionTail": { - "description": "A part of the description, shown under the login-button." + "description": "A part of the description, shown under the login-button.", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "The icon representing this theme.\nUsed as logo in the more-screen and (for official themes) as favicon, webmanifest logo, ...\nEither a URL or a base64 encoded value (which should include 'data:image/svg+xml;base64)\n\nType: icon", @@ -71,7 +103,19 @@ export default { "description": "Define some (overlay) slippy map tilesources", "type": "array", "items": { - "$ref": "#/definitions/default_6" + "allOf": [ + { + "$ref": "#/definitions/RasterLayerProperties" + }, + { + "type": "object", + "properties": { + "defaultState": { + "type": "boolean" + } + } + } + ] } }, "layers": { @@ -98,7 +142,9 @@ export default { } ] }, - "override": {}, + "override": { + "$ref": "#/definitions/Partial" + }, "hideTagRenderingsWithLabels": { "description": "TagRenderings with any of these labels will be removed from the layer.\nNote that the 'id' and 'group' are considered labels too", "type": "array", @@ -118,30 +164,6 @@ export default { ] } }, - "clustering": { - "description": "If defined, data will be clustered.\nDefaults to {maxZoom: 16, minNeeded: 500}", - "anyOf": [ - { - "type": "object", - "properties": { - "maxZoom": { - "description": "All zoom levels above 'maxzoom' are not clustered anymore.\nDefaults to 18", - "type": "number" - }, - "minNeededElements": { - "description": "The number of elements per tile needed to start clustering\nIf clustering is defined, defaults to 250", - "type": "number" - } - } - }, - { - "enum": [ - false - ], - "type": "boolean" - } - ] - }, "customCss": { "description": "The URL of a custom CSS stylesheet to modify the layout", "type": "string" @@ -266,6 +288,10 @@ export default { "overpassTimeout": { "description": "Set a different timeout for overpass queries - in seconds. Default: 30s", "type": "number" + }, + "enableNodeDatabase": { + "description": "Enables tracking of all nodes when data is loaded.\nThis is useful for the 'ImportWay' and 'ConflateWay'-buttons who need this database.\n\nNote: this flag will be automatically set.", + "type": "boolean" } }, "required": [ @@ -335,6 +361,12 @@ export default { "or" ] }, + "Record": { + "type": "object" + }, + "Record>": { + "type": "object" + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -398,9 +430,6 @@ export default { "canonicalDenomination" ] }, - "Record": { - "type": "object" - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -409,10 +438,6 @@ export default { "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -420,11 +445,67 @@ export default { "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -442,6 +523,22 @@ export default { } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -466,7 +563,15 @@ export default { "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", @@ -618,7 +723,7 @@ export default { "type": "object", "properties": { "location": { - "description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint.\n'projected_centerpoint' will show an item on the line itself, near the middle of the line. (LineStrings only)", + "description": "All the locations that this point should be rendered at.\nPossible values are:\n- `point`: only renders points at their location\n- `centroid`: show a symbol at the centerpoint of a (multi)Linestring and (multi)polygon. Points will _not_ be rendered with this\n- `projected_centerpoint`: Only on (multi)linestrings: calculate the centerpoint and snap it to the way\n- `start` and `end`: only on linestrings: add a point to the first/last coordinate of the LineString", "type": "array", "items": { "type": "string" @@ -697,7 +802,7 @@ export default { ] }, "css": { - "description": "A snippet of css code", + "description": "A snippet of css code which is applied onto the container of the entire marker", "anyOf": [ { "$ref": "#/definitions/TagRenderingConfigJson" @@ -708,7 +813,7 @@ export default { ] }, "cssClasses": { - "description": "A snippet of css-classes. They can be space-separated", + "description": "A snippet of css-classes which are applied onto the container of the entire marker. They can be space-separated", "anyOf": [ { "$ref": "#/definitions/TagRenderingConfigJson" @@ -717,6 +822,58 @@ export default { "type": "string" } ] + }, + "labelCss": { + "description": "Css that is applied onto the label", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "labelCssClasses": { + "description": "Css classes that are applied onto the label; can be space-separated", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "pitchAlignment": { + "description": "If the map is pitched, the marker will stay parallel to the screen.\nSet to 'map' if you want to put it flattened on the map", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ] + }, + "rotationAlignment": { + "description": "If the map is rotated, the icon will still point to the north if no rotation was applied", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ] } }, "required": [ @@ -920,7 +1077,7 @@ export default { } }, "inline": { - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present.", "type": "boolean" }, "default": { @@ -947,10 +1104,6 @@ export default { "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -958,11 +1111,67 @@ export default { "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -979,6 +1188,22 @@ export default { "type": "string" } ] + }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] } } }, @@ -1034,7 +1259,7 @@ export default { } }, "inline": { - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present.", "type": "boolean" }, "default": { @@ -1061,10 +1286,6 @@ export default { "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -1072,11 +1293,67 @@ export default { "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -1093,6 +1370,22 @@ export default { "type": "string" } ] + }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] } } }, @@ -1340,42 +1633,63 @@ export default { "Partial": { "type": "object" }, - "default_6": { - "description": "Configuration for a tilesource config", + "RasterLayerProperties": { "type": "object", "properties": { - "id": { - "description": "Id of this overlay, used in the URL-parameters to set the state", - "type": "string" - }, - "source": { - "description": "The path, where {x}, {y} and {z} will be substituted", - "type": "string" + "name": { + "description": "The name of the imagery source", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "isOverlay": { - "description": "Wether or not this is an overlay. Default: true", "type": "boolean" }, - "name": { - "description": "How this will be shown in the selection menu.\nMake undefined if this may not be toggled" + "id": { + "type": "string" }, - "minZoom": { - "description": "Only visible at this or a higher zoom level", + "url": { + "type": "string" + }, + "category": { + "type": "string" + }, + "attribution": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "text": { + "type": "string" + }, + "html": { + "type": "string" + }, + "required": { + "type": "boolean" + } + } + }, + "min_zoom": { "type": "number" }, - "maxZoom": { - "description": "Only visible at this or a lower zoom level", + "max_zoom": { "type": "number" }, - "defaultState": { - "description": "The default state, set to false to hide by default", + "best": { "type": "boolean" } }, "required": [ - "defaultState", "id", - "source" + "name", + "url" ] }, "LayerConfigJson": { @@ -1387,90 +1701,80 @@ export default { "type": "string" }, "name": { - "description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control" - }, - "description": { - "description": "A description for this layer.\nShown in the layer selections and in the personel theme" - }, - "source": { - "description": "This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.\n\nIf no 'geojson' is defined, data will be fetched from overpass and the OSM-API.\n\nEvery source _must_ define which tags _must_ be present in order to be picked up.", + "description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control", "anyOf": [ { - "allOf": [ - { - "type": "object", - "properties": { - "osmTags": { - "$ref": "#/definitions/TagConfigJson", - "description": "Every source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", - "type": "number" - } - }, - "required": [ - "osmTags" - ] + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "description": { + "description": "A description for this layer.\nShown in the layer selections and in the personel theme", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "source": { + "description": "This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.\n\nIf no 'geojson' is defined, data will be fetched from overpass and the OSM-API.\n\nEvery source _must_ define which tags _must_ be present in order to be picked up.\n\nNote: a source must always be defined. 'special' is only allowed if this is a builtin-layer", + "anyOf": [ + { + "type": "object", + "properties": { + "osmTags": { + "$ref": "#/definitions/TagConfigJson", + "description": "Every source must set which tags have to be present in order to load the given layer." }, - { - "type": "object", - "properties": { - "overpassScript": { - "description": "If set, this custom overpass-script will be used instead of building one by using the OSM-tags.\nSpecifying OSM-tags is still obligatory and will still hide non-matching items and they will be used for the rest of the pipeline.\n_This should be really rare_.\n\nFor example, when you want to fetch all grass-areas in parks and which are marked as publicly accessible:\n```\n\"source\": {\n \"overpassScript\":\n \"way[\\\"leisure\\\"=\\\"park\\\"];node(w);is_in;area._[\\\"leisure\\\"=\\\"park\\\"];(way(area)[\\\"landuse\\\"=\\\"grass\\\"]; node(w); );\",\n \"osmTags\": \"access=yes\"\n}\n```", - "type": "string" - } - } + "maxCacheAge": { + "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", + "type": "number" } + }, + "required": [ + "osmTags" ] }, { - "allOf": [ - { - "type": "object", - "properties": { - "osmTags": { - "$ref": "#/definitions/TagConfigJson", - "description": "Every source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", - "type": "number" - } - }, - "required": [ - "osmTags" - ] + "type": "object", + "properties": { + "geoJson": { + "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}", + "type": "string" }, - { - "type": "object", - "properties": { - "geoJson": { - "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}", - "type": "string" - }, - "geoJsonZoomLevel": { - "description": "To load a tiled geojson layer, set the zoomlevel of the tiles", - "type": "number" - }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache", - "type": "boolean" - }, - "mercatorCrs": { - "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this", - "type": "boolean" - }, - "idKey": { - "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'", - "type": "string" - } - }, - "required": [ - "geoJson" - ] + "geoJsonZoomLevel": { + "description": "To load a tiled geojson layer, set the zoomlevel of the tiles", + "type": "number" + }, + "isOsmCache": { + "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache", + "type": "boolean" + }, + "mercatorCrs": { + "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this", + "type": "boolean" + }, + "idKey": { + "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'", + "type": "string" } + }, + "required": [ + "geoJson" ] + }, + { + "enum": [ + "special", + "special:library" + ], + "type": "string" } ] }, @@ -1594,7 +1898,15 @@ export default { "type": "object", "properties": { "title": { - "description": "The title - shown on the 'add-new'-button.\n\nThis should include the article of the noun, e.g. 'a hydrant', 'a bicycle pump'.\nThis text will be inserted into `Add {category} here`, becoming `Add a hydrant here`.\n\nDo _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!" + "description": "The title - shown on the 'add-new'-button.\n\nThis should include the article of the noun, e.g. 'a hydrant', 'a bicycle pump'.\nThis text will be inserted into `Add {category} here`, becoming `Add a hydrant here`.\n\nDo _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "tags": { "description": "The tags to add. It determines the icon too", @@ -1604,7 +1916,15 @@ export default { } }, "description": { - "description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)" + "description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "exampleImages": { "description": "Example images, which show real-life pictures of what such a feature might look like\n\nType: image", @@ -1679,6 +1999,9 @@ export default { { "type": "object", "properties": { + "id": { + "type": "string" + }, "builtin": { "anyOf": [ { @@ -1701,28 +2024,6 @@ export default { "override" ] }, - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "builtin": { - "type": "array", - "items": { - "type": "string" - } - }, - "override": { - "$ref": "#/definitions/Partial" - } - }, - "required": [ - "builtin", - "id", - "override" - ] - }, { "allOf": [ { @@ -1829,6 +2130,438 @@ export default { "source" ] }, + "Partial": { + "type": "object", + "properties": { + "id": { + "description": "The id of this layer.\nThis should be a simple, lowercase, human readable string that is used to identify the layer.", + "type": "string" + }, + "name": { + "description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "description": { + "description": "A description for this layer.\nShown in the layer selections and in the personel theme", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "source": { + "description": "This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.\n\nIf no 'geojson' is defined, data will be fetched from overpass and the OSM-API.\n\nEvery source _must_ define which tags _must_ be present in order to be picked up.\n\nNote: a source must always be defined. 'special' is only allowed if this is a builtin-layer", + "anyOf": [ + { + "type": "object", + "properties": { + "osmTags": { + "$ref": "#/definitions/TagConfigJson", + "description": "Every source must set which tags have to be present in order to load the given layer." + }, + "maxCacheAge": { + "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", + "type": "number" + } + }, + "required": [ + "osmTags" + ] + }, + { + "type": "object", + "properties": { + "geoJson": { + "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}", + "type": "string" + }, + "geoJsonZoomLevel": { + "description": "To load a tiled geojson layer, set the zoomlevel of the tiles", + "type": "number" + }, + "isOsmCache": { + "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache", + "type": "boolean" + }, + "mercatorCrs": { + "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this", + "type": "boolean" + }, + "idKey": { + "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'", + "type": "string" + } + }, + "required": [ + "geoJson" + ] + }, + { + "enum": [ + "special", + "special:library" + ], + "type": "string" + } + ] + }, + "calculatedTags": { + "description": "A list of extra tags to calculate, specified as \"keyToAssignTo=javascript-expression\".\nThere are a few extra functions available. Refer to Docs/CalculatedTags.md for more information\nThe functions will be run in order, e.g.\n[\n \"_max_overlap_m2=Math.max(...feat.overlapsWith(\"someOtherLayer\").map(o => o.overlap))\n \"_max_overlap_ratio=Number(feat._max_overlap_m2)/feat.area\n]\n\nThe specified tags are evaluated lazily. E.g. if a calculated tag is only used in the popup (e.g. the number of nearby features),\nthe expensive calculation will only be performed then for that feature. This avoids clogging up the contributors PC when all features are loaded.\n\nIf a tag has to be evaluated strictly, use ':=' instead:\n\n[\n\"_some_key:=some_javascript_expression\"\n]", + "type": "array", + "items": { + "type": "string" + } + }, + "doNotDownload": { + "description": "If set, this layer will not query overpass; but it'll still match the tags above which are by chance returned by other layers.\nWorks well together with 'passAllFeatures', to add decoration", + "type": "boolean" + }, + "isShown": { + "description": "If set, only features matching this extra tag will be shown.\nThis is useful to hide certain features from view.\n\nImportant: hiding features does not work dynamically, but is only calculated when the data is first renders.\nThis implies that it is not possible to hide a feature after a tagging change\n\nThe default value is 'yes'", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, + "forceLoad": { + "description": "Advanced option - might be set by the theme compiler\n\nIf true, this data will _always_ be loaded, even if the theme is disabled", + "type": "boolean" + }, + "minzoom": { + "description": "The minimum needed zoomlevel required before loading of the data start\nDefault: 0", + "type": "number" + }, + "shownByDefault": { + "description": "Indicates if this layer is shown by default;\ncan be used to hide a layer from start, or to load the layer but only to show it where appropriate (e.g. for snapping to it)", + "type": "boolean" + }, + "minzoomVisible": { + "description": "The zoom level at which point the data is hidden again\nDefault: 100 (thus: always visible", + "type": "number" + }, + "title": { + "description": "The title shown in a popup for elements of this layer.", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "titleIcons": { + "description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\n\nType: icon[]", + "anyOf": [ + { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + } + }, + { + "type": "array", + "items": [ + { + "type": "string", + "enum": [ + "defaults" + ] + } + ], + "minItems": 1, + "maxItems": 1 + } + ] + }, + "mapRendering": { + "description": "Visualisation of the items on the map", + "anyOf": [ + { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/default_4" + }, + { + "$ref": "#/definitions/default_5" + }, + { + "$ref": "#/definitions/default" + } + ] + } + }, + { + "type": "null" + } + ] + }, + "passAllFeatures": { + "description": "If set, this layer will pass all the features it receives onto the next layer.\nThis is ideal for decoration, e.g. directionss on cameras", + "type": "boolean" + }, + "presets": { + "description": "Presets for this layer.\nA preset shows up when clicking the map on a without data (or when right-clicking/long-pressing);\nit will prompt the user to add a new point.\n\nThe most important aspect are the tags, which define which tags the new point will have;\nThe title is shown in the dialog, along with the first sentence of the description.\n\nUpon confirmation, the full description is shown beneath the buttons - perfect to add pictures and examples.\n\nNote: the icon of the preset is determined automatically based on the tags and the icon above. Don't worry about that!\nNB: if no presets are defined, the popup to add new points doesn't show up at all", + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "description": "The title - shown on the 'add-new'-button.\n\nThis should include the article of the noun, e.g. 'a hydrant', 'a bicycle pump'.\nThis text will be inserted into `Add {category} here`, becoming `Add a hydrant here`.\n\nDo _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "tags": { + "description": "The tags to add. It determines the icon too", + "type": "array", + "items": { + "type": "string" + } + }, + "description": { + "description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] + }, + "exampleImages": { + "description": "Example images, which show real-life pictures of what such a feature might look like\n\nType: image", + "type": "array", + "items": { + "type": "string" + } + }, + "preciseInput": { + "description": "If set, the user will prompted to confirm the location before actually adding the data.\nThis will be with a 'drag crosshair'-method.\n\nIf 'preferredBackgroundCategory' is set, the element will attempt to pick a background layer of that category.", + "anyOf": [ + { + "type": "object", + "properties": { + "preferredBackground": { + "description": "The type of background picture", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "snapToLayer": { + "description": "If specified, these layers will be shown to and the new point will be snapped towards it", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "maxSnapDistance": { + "description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10", + "type": "number" + } + } + }, + { + "enum": [ + true + ], + "type": "boolean" + } + ] + } + }, + "required": [ + "tags", + "title" + ] + } + }, + "tagRenderings": { + "description": "All the tag renderings.\nA tag rendering is a block that either shows the known value or asks a question.\n\nRefer to the class `TagRenderingConfigJson` to see the possibilities.\n\nNote that we can also use a string here - where the string refers to a tag rendering defined in `assets/questions/questions.json`,\nwhere a few very general questions are defined e.g. website, phone number, ...\nFurthermore, _all_ the questions of another layer can be reused with `otherlayer.*`\nIf you need only a single of the tagRenderings, use `otherlayer.tagrenderingId`\nIf one or more questions have a 'group' or 'label' set, select all the entries with the corresponding group or label with `otherlayer.*group`\nRemark: if a tagRendering is 'lent' from another layer, the 'source'-tags are copied and added as condition.\nIf they are not wanted, remove them with an override\n\nA special value is 'questions', which indicates the location of the questions box. If not specified, it'll be appended to the bottom of the featureInfobox.\n\nAt last, one can define a group of renderings where parts of all strings will be replaced by multiple other strings.\nThis is mainly create questions for a 'left' and a 'right' side of the road.\nThese will be grouped and questions will be asked together", + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/QuestionableTagRenderingConfigJson" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "builtin": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "override": { + "$ref": "#/definitions/Partial" + } + }, + "required": [ + "builtin", + "override" + ] + }, + { + "allOf": [ + { + "$ref": "#/definitions/default<(string|QuestionableTagRenderingConfigJson|{builtin:string;override:Partial;})[]>" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + ] + }, + { + "type": "string" + } + ] + } + }, + "filter": { + "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in 'filters.json' for the appropriate filter or will try to parse it as `layername.filterid` and us that one", + "anyOf": [ + { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/default_1" + }, + { + "type": "string" + } + ] + } + }, + { + "type": "object", + "properties": { + "sameAs": { + "type": "string" + } + }, + "required": [ + "sameAs" + ] + } + ] + }, + "deletion": { + "description": "This block defines under what circumstances the delete dialog is shown for objects of this layer.\nIf set, a dialog is shown to the user to (soft) delete the point.\nThe dialog is built to be user friendly and to prevent mistakes.\nIf deletion is not possible, the dialog will hide itself and show the reason of non-deletability instead.\n\nTo configure, the following values are possible:\n\n- false: never ever show the delete button\n- true: show the default delete button\n- undefined: use the mapcomplete default to show deletion or not. Currently, this is the same as 'false' but this will change in the future\n- or: a hash with options (see below)\n\n The delete dialog\n =================\n\n\n\n#### Hard deletion if enough experience\n\nA feature can only be deleted from OpenStreetMap by mapcomplete if:\n\n- It is a node\n- No ways or relations use the node\n- The logged-in user has enough experience OR the user is the only one to have edited the point previously\n- The logged-in user has no unread messages (or has a ton of experience)\n- The user did not select one of the 'non-delete-options' (see below)\n\nIn all other cases, a 'soft deletion' is used.\n\n#### Soft deletion\n\nA 'soft deletion' is when the point isn't deleted from OSM but retagged so that it'll won't how up in the mapcomplete theme anymore.\nThis makes it look like it was deleted, without doing damage. A fixme will be added to the point.\n\nNote that a soft deletion is _only_ possible if these tags are provided by the theme creator, as they'll be different for every theme\n\n#### No-delete options\n\nIn some cases, the contributor might want to delete something for the wrong reason (e.g. someone who wants to have a path removed \"because the path is on their private property\").\nHowever, the path exists in reality and should thus be on OSM - otherwise the next contributor will pass by and notice \"hey, there is a path missing here! Let me redraw it in OSM!)\n\nThe correct approach is to retag the feature in such a way that it is semantically correct *and* that it doesn't show up on the theme anymore.\nA no-delete option is offered as 'reason to delete it', but secretly retags.", + "anyOf": [ + { + "$ref": "#/definitions/DeleteConfigJson" + }, + { + "type": "boolean" + } + ] + }, + "allowMove": { + "description": "Indicates if a point can be moved and configures the modalities.\n\nA feature can be moved by MapComplete if:\n\n- It is a point\n- The point is _not_ part of a way or a a relation.\n\nOff by default. Can be enabled by setting this flag or by configuring.", + "anyOf": [ + { + "$ref": "#/definitions/default_3" + }, + { + "type": "boolean" + } + ] + }, + "allowSplit": { + "description": "If set, a 'split this way' button is shown on objects rendered as LineStrings, e.g. highways.\n\nIf the way is part of a relation, MapComplete will attempt to update this relation as well", + "type": "boolean" + }, + "units": { + "type": "array", + "items": { + "$ref": "#/definitions/default_2" + } + }, + "syncSelection": { + "description": "If set, synchronizes whether or not this layer is enabled.\n\nno: Do not sync at all, always revert to default\nlocal: keep selection on local storage\ntheme-only: sync via OSM, but this layer will only be toggled in this theme\nglobal: all layers with this ID will be synced accross all themes", + "enum": [ + "global", + "local", + "no", + "theme-only" + ], + "type": "string" + }, + "#": { + "description": "Used for comments and/or to disable some checks\n\nno-question-hint-check: disables a check in MiscTagRenderingChecks which complains about 'div', 'span' or 'class=subtle'-HTML elements in the tagRendering", + "type": "string" + } + } + }, "default": { "type": "object", "properties": { diff --git a/Docs/Schemas/LineRenderingConfigJson.schema.json b/Docs/Schemas/LineRenderingConfigJson.schema.json index c76fd695f5..744b13d2b7 100644 --- a/Docs/Schemas/LineRenderingConfigJson.schema.json +++ b/Docs/Schemas/LineRenderingConfigJson.schema.json @@ -146,6 +146,14 @@ ], "additionalProperties": false }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "Record>": { + "type": "object", + "additionalProperties": false + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -210,10 +218,6 @@ ], "additionalProperties": false }, - "Record": { - "type": "object", - "additionalProperties": false - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -222,10 +226,6 @@ "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -233,11 +233,67 @@ "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -255,6 +311,22 @@ } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -279,7 +351,15 @@ "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", diff --git a/Docs/Schemas/LineRenderingConfigJsonJSC.ts b/Docs/Schemas/LineRenderingConfigJsonJSC.ts index ba475b1cee..db7177b6f9 100644 --- a/Docs/Schemas/LineRenderingConfigJsonJSC.ts +++ b/Docs/Schemas/LineRenderingConfigJsonJSC.ts @@ -144,6 +144,12 @@ export default { "or" ] }, + "Record": { + "type": "object" + }, + "Record>": { + "type": "object" + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -207,9 +213,6 @@ export default { "canonicalDenomination" ] }, - "Record": { - "type": "object" - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -218,10 +221,6 @@ export default { "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -229,11 +228,67 @@ export default { "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -251,6 +306,22 @@ export default { } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -275,7 +346,15 @@ export default { "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", diff --git a/Docs/Schemas/MappingConfigJson.schema.json b/Docs/Schemas/MappingConfigJson.schema.json index 2a33419bb5..2b5926dd83 100644 --- a/Docs/Schemas/MappingConfigJson.schema.json +++ b/Docs/Schemas/MappingConfigJson.schema.json @@ -162,6 +162,14 @@ ], "additionalProperties": false }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "Record>": { + "type": "object", + "additionalProperties": false + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -226,10 +234,6 @@ ], "additionalProperties": false }, - "Record": { - "type": "object", - "additionalProperties": false - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -238,10 +242,6 @@ "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -249,11 +249,67 @@ "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -271,6 +327,22 @@ } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -295,7 +367,15 @@ "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", diff --git a/Docs/Schemas/MappingConfigJsonJSC.ts b/Docs/Schemas/MappingConfigJsonJSC.ts index d7ae48b03f..b53c9837b4 100644 --- a/Docs/Schemas/MappingConfigJsonJSC.ts +++ b/Docs/Schemas/MappingConfigJsonJSC.ts @@ -160,6 +160,12 @@ export default { "or" ] }, + "Record": { + "type": "object" + }, + "Record>": { + "type": "object" + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -223,9 +229,6 @@ export default { "canonicalDenomination" ] }, - "Record": { - "type": "object" - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -234,10 +237,6 @@ export default { "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -245,11 +244,67 @@ export default { "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -267,6 +322,22 @@ export default { } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -291,7 +362,15 @@ export default { "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", diff --git a/Docs/Schemas/MoveConfigJson.schema.json b/Docs/Schemas/MoveConfigJson.schema.json index b851d9fbb3..33c89a5bae 100644 --- a/Docs/Schemas/MoveConfigJson.schema.json +++ b/Docs/Schemas/MoveConfigJson.schema.json @@ -69,6 +69,14 @@ ], "additionalProperties": false }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "Record>": { + "type": "object", + "additionalProperties": false + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -132,10 +140,6 @@ "canonicalDenomination" ], "additionalProperties": false - }, - "Record": { - "type": "object", - "additionalProperties": false } }, "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/Docs/Schemas/MoveConfigJsonJSC.ts b/Docs/Schemas/MoveConfigJsonJSC.ts index f963559b92..2d2565825e 100644 --- a/Docs/Schemas/MoveConfigJsonJSC.ts +++ b/Docs/Schemas/MoveConfigJsonJSC.ts @@ -67,6 +67,12 @@ export default { "or" ] }, + "Record": { + "type": "object" + }, + "Record>": { + "type": "object" + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -129,9 +135,6 @@ export default { "required": [ "canonicalDenomination" ] - }, - "Record": { - "type": "object" } }, "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/Docs/Schemas/PointRenderingConfigJson.schema.json b/Docs/Schemas/PointRenderingConfigJson.schema.json index cb5fbcbf27..a8c0069fda 100644 --- a/Docs/Schemas/PointRenderingConfigJson.schema.json +++ b/Docs/Schemas/PointRenderingConfigJson.schema.json @@ -3,7 +3,7 @@ "type": "object", "properties": { "location": { - "description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint.\n'projected_centerpoint' will show an item on the line itself, near the middle of the line. (LineStrings only)", + "description": "All the locations that this point should be rendered at.\nPossible values are:\n- `point`: only renders points at their location\n- `centroid`: show a symbol at the centerpoint of a (multi)Linestring and (multi)polygon. Points will _not_ be rendered with this\n- `projected_centerpoint`: Only on (multi)linestrings: calculate the centerpoint and snap it to the way\n- `start` and `end`: only on linestrings: add a point to the first/last coordinate of the LineString", "type": "array", "items": { "type": "string" @@ -82,7 +82,7 @@ ] }, "css": { - "description": "A snippet of css code", + "description": "A snippet of css code which is applied onto the container of the entire marker", "anyOf": [ { "$ref": "#/definitions/TagRenderingConfigJson" @@ -93,7 +93,7 @@ ] }, "cssClasses": { - "description": "A snippet of css-classes. They can be space-separated", + "description": "A snippet of css-classes which are applied onto the container of the entire marker. They can be space-separated", "anyOf": [ { "$ref": "#/definitions/TagRenderingConfigJson" @@ -102,6 +102,58 @@ "type": "string" } ] + }, + "labelCss": { + "description": "Css that is applied onto the label", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "labelCssClasses": { + "description": "Css classes that are applied onto the label; can be space-separated", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "pitchAlignment": { + "description": "If the map is pitched, the marker will stay parallel to the screen.\nSet to 'map' if you want to put it flattened on the map", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ] + }, + "rotationAlignment": { + "description": "If the map is rotated, the icon will still point to the north if no rotation was applied", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ] } }, "required": [ @@ -166,6 +218,14 @@ ], "additionalProperties": false }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "Record>": { + "type": "object", + "additionalProperties": false + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -230,10 +290,6 @@ ], "additionalProperties": false }, - "Record": { - "type": "object", - "additionalProperties": false - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -242,10 +298,6 @@ "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -253,11 +305,67 @@ "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -275,6 +383,22 @@ } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -299,7 +423,15 @@ "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", diff --git a/Docs/Schemas/PointRenderingConfigJsonJSC.ts b/Docs/Schemas/PointRenderingConfigJsonJSC.ts index 0a63aafeaa..974a1735f8 100644 --- a/Docs/Schemas/PointRenderingConfigJsonJSC.ts +++ b/Docs/Schemas/PointRenderingConfigJsonJSC.ts @@ -3,7 +3,7 @@ export default { "type": "object", "properties": { "location": { - "description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint.\n'projected_centerpoint' will show an item on the line itself, near the middle of the line. (LineStrings only)", + "description": "All the locations that this point should be rendered at.\nPossible values are:\n- `point`: only renders points at their location\n- `centroid`: show a symbol at the centerpoint of a (multi)Linestring and (multi)polygon. Points will _not_ be rendered with this\n- `projected_centerpoint`: Only on (multi)linestrings: calculate the centerpoint and snap it to the way\n- `start` and `end`: only on linestrings: add a point to the first/last coordinate of the LineString", "type": "array", "items": { "type": "string" @@ -82,7 +82,7 @@ export default { ] }, "css": { - "description": "A snippet of css code", + "description": "A snippet of css code which is applied onto the container of the entire marker", "anyOf": [ { "$ref": "#/definitions/TagRenderingConfigJson" @@ -93,7 +93,7 @@ export default { ] }, "cssClasses": { - "description": "A snippet of css-classes. They can be space-separated", + "description": "A snippet of css-classes which are applied onto the container of the entire marker. They can be space-separated", "anyOf": [ { "$ref": "#/definitions/TagRenderingConfigJson" @@ -102,6 +102,58 @@ export default { "type": "string" } ] + }, + "labelCss": { + "description": "Css that is applied onto the label", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "labelCssClasses": { + "description": "Css classes that are applied onto the label; can be space-separated", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + }, + "pitchAlignment": { + "description": "If the map is pitched, the marker will stay parallel to the screen.\nSet to 'map' if you want to put it flattened on the map", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ] + }, + "rotationAlignment": { + "description": "If the map is rotated, the icon will still point to the north if no rotation was applied", + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ] } }, "required": [ @@ -164,6 +216,12 @@ export default { "or" ] }, + "Record": { + "type": "object" + }, + "Record>": { + "type": "object" + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -227,9 +285,6 @@ export default { "canonicalDenomination" ] }, - "Record": { - "type": "object" - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -238,10 +293,6 @@ export default { "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -249,11 +300,67 @@ export default { "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -271,6 +378,22 @@ export default { } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -295,7 +418,15 @@ export default { "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", diff --git a/Docs/Schemas/QuestionableTagRenderingConfigJson.schema.json b/Docs/Schemas/QuestionableTagRenderingConfigJson.schema.json index f7529d6d2a..2eabe2ca60 100644 --- a/Docs/Schemas/QuestionableTagRenderingConfigJson.schema.json +++ b/Docs/Schemas/QuestionableTagRenderingConfigJson.schema.json @@ -51,7 +51,7 @@ } }, "inline": { - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present.", "type": "boolean" }, "default": { @@ -78,10 +78,6 @@ "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -89,11 +85,67 @@ "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -110,6 +162,22 @@ "type": "string" } ] + }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] } }, "definitions": { @@ -171,6 +239,14 @@ ], "additionalProperties": false }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "Record>": { + "type": "object", + "additionalProperties": false + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -235,10 +311,6 @@ ], "additionalProperties": false }, - "Record": { - "type": "object", - "additionalProperties": false - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -247,10 +319,6 @@ "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -258,11 +326,67 @@ "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -280,6 +404,22 @@ } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -304,7 +444,15 @@ "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", diff --git a/Docs/Schemas/QuestionableTagRenderingConfigJsonJSC.ts b/Docs/Schemas/QuestionableTagRenderingConfigJsonJSC.ts index 8db0039d61..b0b2b16893 100644 --- a/Docs/Schemas/QuestionableTagRenderingConfigJsonJSC.ts +++ b/Docs/Schemas/QuestionableTagRenderingConfigJsonJSC.ts @@ -51,7 +51,7 @@ export default { } }, "inline": { - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present.", "type": "boolean" }, "default": { @@ -78,10 +78,6 @@ export default { "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -89,11 +85,67 @@ export default { "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -110,6 +162,22 @@ export default { "type": "string" } ] + }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] } }, "definitions": { @@ -169,6 +237,12 @@ export default { "or" ] }, + "Record": { + "type": "object" + }, + "Record>": { + "type": "object" + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -232,9 +306,6 @@ export default { "canonicalDenomination" ] }, - "Record": { - "type": "object" - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -243,10 +314,6 @@ export default { "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -254,11 +321,67 @@ export default { "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -276,6 +399,22 @@ export default { } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -300,7 +439,15 @@ export default { "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", diff --git a/Docs/Schemas/RewritableConfigJson.schema.json b/Docs/Schemas/RewritableConfigJson.schema.json index 477a3f981b..01dbd836fa 100644 --- a/Docs/Schemas/RewritableConfigJson.schema.json +++ b/Docs/Schemas/RewritableConfigJson.schema.json @@ -91,6 +91,14 @@ ], "additionalProperties": false }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "Record>": { + "type": "object", + "additionalProperties": false + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -155,10 +163,6 @@ ], "additionalProperties": false }, - "Record": { - "type": "object", - "additionalProperties": false - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -167,10 +171,6 @@ "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -178,11 +178,67 @@ "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -200,6 +256,22 @@ } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -224,7 +296,15 @@ "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", diff --git a/Docs/Schemas/RewritableConfigJsonJSC.ts b/Docs/Schemas/RewritableConfigJsonJSC.ts index 6c1e7fac5a..b173395d57 100644 --- a/Docs/Schemas/RewritableConfigJsonJSC.ts +++ b/Docs/Schemas/RewritableConfigJsonJSC.ts @@ -89,6 +89,12 @@ export default { "or" ] }, + "Record": { + "type": "object" + }, + "Record>": { + "type": "object" + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -152,9 +158,6 @@ export default { "canonicalDenomination" ] }, - "Record": { - "type": "object" - }, "TagRenderingConfigJson": { "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one", "type": "object", @@ -163,10 +166,6 @@ export default { "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -174,11 +173,67 @@ export default { "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -196,6 +251,22 @@ export default { } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -220,7 +291,15 @@ export default { "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", diff --git a/Docs/Schemas/TagRenderingConfigJson.schema.json b/Docs/Schemas/TagRenderingConfigJson.schema.json index 1f09125f47..3453db4dee 100644 --- a/Docs/Schemas/TagRenderingConfigJson.schema.json +++ b/Docs/Schemas/TagRenderingConfigJson.schema.json @@ -6,10 +6,6 @@ "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -17,11 +13,67 @@ "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -39,6 +91,22 @@ } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -63,7 +131,15 @@ "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", @@ -155,6 +231,14 @@ "or" ], "additionalProperties": false + }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "Record>": { + "type": "object", + "additionalProperties": false } }, "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/Docs/Schemas/TagRenderingConfigJsonJSC.ts b/Docs/Schemas/TagRenderingConfigJsonJSC.ts index f3823873a0..c2bdd5fe09 100644 --- a/Docs/Schemas/TagRenderingConfigJsonJSC.ts +++ b/Docs/Schemas/TagRenderingConfigJsonJSC.ts @@ -6,10 +6,6 @@ export default { "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)", "type": "string" }, - "group": { - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element.", - "type": "string" - }, "labels": { "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away", "type": "array", @@ -17,11 +13,67 @@ export default { "type": "string" } }, + "classes": { + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "description": { - "description": "A human-readable text explaining what this tagRendering does" + "description": "A human-readable text explaining what this tagRendering does", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "render": { - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ] }, "condition": { "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```", @@ -39,6 +91,22 @@ export default { } ] }, + "metacondition": { + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_", + "anyOf": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ] + }, "freeform": { "description": "Allow freeform text input from the user", "type": "object", @@ -63,7 +131,15 @@ export default { "description": "If this condition is met, then the text under `then` will be shown.\nIf no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.\n\nFor example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}\n\nThis can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}" }, "then": { - "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "icon": { "description": "An icon supporting this mapping; typically shown pretty small\nType: icon", @@ -153,6 +229,12 @@ export default { "required": [ "or" ] + }, + "Record": { + "type": "object" + }, + "Record>": { + "type": "object" } }, "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/Docs/Schemas/UnitConfigJson.schema.json b/Docs/Schemas/UnitConfigJson.schema.json index 6d885363bb..6249972229 100644 --- a/Docs/Schemas/UnitConfigJson.schema.json +++ b/Docs/Schemas/UnitConfigJson.schema.json @@ -88,6 +88,14 @@ ], "additionalProperties": false }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "Record>": { + "type": "object", + "additionalProperties": false + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -151,10 +159,6 @@ "canonicalDenomination" ], "additionalProperties": false - }, - "Record": { - "type": "object", - "additionalProperties": false } }, "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/Docs/Schemas/UnitConfigJsonJSC.ts b/Docs/Schemas/UnitConfigJsonJSC.ts index b4b06c4694..857c15586d 100644 --- a/Docs/Schemas/UnitConfigJsonJSC.ts +++ b/Docs/Schemas/UnitConfigJsonJSC.ts @@ -86,6 +86,12 @@ export default { "or" ] }, + "Record": { + "type": "object" + }, + "Record>": { + "type": "object" + }, "DenominationConfigJson": { "type": "object", "properties": { @@ -148,9 +154,6 @@ export default { "required": [ "canonicalDenomination" ] - }, - "Record": { - "type": "object" } }, "$schema": "http://json-schema.org/draft-07/schema#" From e4e99c0b0a2e7c1aa383c33c87aae39777d6edaf Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 21 Apr 2023 16:19:36 +0200 Subject: [PATCH 056/257] Chore: move RasterLayerProperties into separate file in order for the schema generator to work --- Models/RasterLayerProperties.ts | 34 + Models/RasterLayers.ts | 36 +- Models/ThemeConfig/Json/LayoutConfigJson.ts | 3 +- .../ThemeConfig/Json/TilesourceConfigJson.ts | 38 - Models/ThemeConfig/LayoutConfig.ts | 3 +- UI/BigComponents/OverlayToggle.svelte | 2 +- UI/Map/MapLibreAdaptor.ts | 3 +- UI/Map/ShowOverlayRasterLayer.ts | 2 +- assets/layoutconfigmeta.json | 60471 +++++++++++++++- .../questionabletagrenderingconfigmeta.json | 185 +- assets/tagrenderingconfigmeta.json | 191 +- 11 files changed, 59342 insertions(+), 1626 deletions(-) create mode 100644 Models/RasterLayerProperties.ts delete mode 100644 Models/ThemeConfig/Json/TilesourceConfigJson.ts diff --git a/Models/RasterLayerProperties.ts b/Models/RasterLayerProperties.ts new file mode 100644 index 0000000000..a4bf0e09bc --- /dev/null +++ b/Models/RasterLayerProperties.ts @@ -0,0 +1,34 @@ +export interface RasterLayerProperties { + /** + * The name of the imagery source + */ + readonly name: string | Record + + readonly isOverlay?: boolean + + readonly id: string + + readonly url: string + readonly category?: + | string + | "photo" + | "map" + | "historicmap" + | "osmbasedmap" + | "historicphoto" + | "qa" + | "elevation" + | "other" + + readonly attribution?: { + readonly url?: string + readonly text?: string + readonly html?: string + readonly required?: boolean + } + + readonly min_zoom?: number + readonly max_zoom?: number + + readonly best?: boolean +} diff --git a/Models/RasterLayers.ts b/Models/RasterLayers.ts index ef2471dfa9..0602015f7b 100644 --- a/Models/RasterLayers.ts +++ b/Models/RasterLayers.ts @@ -4,6 +4,7 @@ import * as globallayers from "../assets/global-raster-layers.json" import { BBox } from "../Logic/BBox" import { Store, Stores } from "../Logic/UIEventSource" import { GeoOperations } from "../Logic/GeoOperations" +import { RasterLayerProperties } from "./RasterLayerProperties" export class AvailableRasterLayers { public static EditorLayerIndex: (Feature & @@ -119,41 +120,6 @@ export class RasterLayerUtils { export type RasterLayerPolygon = Feature -export interface RasterLayerProperties { - /** - * The name of the imagery source - */ - readonly name: string | Record - - readonly isOverlay?: boolean - - readonly id: string - - readonly url: string - readonly category?: - | string - | "photo" - | "map" - | "historicmap" - | "osmbasedmap" - | "historicphoto" - | "qa" - | "elevation" - | "other" - - readonly attribution?: { - readonly url?: string - readonly text?: string - readonly html?: string - readonly required?: boolean - } - - readonly min_zoom?: number - readonly max_zoom?: number - - readonly best?: boolean -} - /** * Information about a raster tile layer * diff --git a/Models/ThemeConfig/Json/LayoutConfigJson.ts b/Models/ThemeConfig/Json/LayoutConfigJson.ts index ca32942dd1..9aa8c2e650 100644 --- a/Models/ThemeConfig/Json/LayoutConfigJson.ts +++ b/Models/ThemeConfig/Json/LayoutConfigJson.ts @@ -1,6 +1,7 @@ import { LayerConfigJson } from "./LayerConfigJson" import ExtraLinkConfigJson from "./ExtraLinkConfigJson" -import { RasterLayerProperties } from "../../RasterLayers" + +import { RasterLayerProperties } from "../../RasterLayerProperties" /** * Defines the entire theme. diff --git a/Models/ThemeConfig/Json/TilesourceConfigJson.ts b/Models/ThemeConfig/Json/TilesourceConfigJson.ts deleted file mode 100644 index ca80ed0c65..0000000000 --- a/Models/ThemeConfig/Json/TilesourceConfigJson.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Configuration for a tilesource config - */ -export default interface TilesourceConfigJson { - /** - * Id of this overlay, used in the URL-parameters to set the state - */ - id: string - /** - * The path, where {x}, {y} and {z} will be substituted - */ - source: string - /** - * Wether or not this is an overlay. Default: true - */ - isOverlay?: boolean - - /** - * How this will be shown in the selection menu. - * Make undefined if this may not be toggled - */ - name?: any | string - - /** - * Only visible at this or a higher zoom level - */ - minZoom?: number - - /** - * Only visible at this or a lower zoom level - */ - maxZoom?: number - - /** - * The default state, set to false to hide by default - */ - defaultState: boolean -} diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts index b6db493bbc..c55eb95369 100644 --- a/Models/ThemeConfig/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -7,7 +7,8 @@ import { ExtractImages } from "./Conversion/FixImages" import ExtraLinkConfig from "./ExtraLinkConfig" import { Utils } from "../../Utils" import LanguageUtils from "../../Utils/LanguageUtils" -import { RasterLayerProperties } from "../RasterLayers" + +import { RasterLayerProperties } from "../RasterLayerProperties" /** * Minimal information about a theme diff --git a/UI/BigComponents/OverlayToggle.svelte b/UI/BigComponents/OverlayToggle.svelte index c774abb357..3b350b285c 100644 --- a/UI/BigComponents/OverlayToggle.svelte +++ b/UI/BigComponents/OverlayToggle.svelte @@ -6,8 +6,8 @@ import { onDestroy } from "svelte"; import { UIEventSource } from "../../Logic/UIEventSource"; import Tr from "../Base/Tr.svelte"; import Translations from "../i18n/Translations"; -import type { RasterLayerProperties } from "../../Models/RasterLayers"; import { Translation } from "../i18n/Translation"; +import { RasterLayerProperties } from "../../Models/RasterLayerProperties"; export let layerproperties : RasterLayerProperties export let state: {isDisplayed: UIEventSource}; diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index acc9ac1d6a..dd618d912a 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -1,13 +1,14 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" import type { Map as MLMap } from "maplibre-gl" import { Map as MlMap, SourceSpecification } from "maplibre-gl" -import { RasterLayerPolygon, RasterLayerProperties } from "../../Models/RasterLayers" +import { RasterLayerPolygon } from "../../Models/RasterLayers" import { Utils } from "../../Utils" import { BBox } from "../../Logic/BBox" import { ExportableMap, MapProperties } from "../../Models/MapProperties" import SvelteUIElement from "../Base/SvelteUIElement" import MaplibreMap from "./MaplibreMap.svelte" import html2canvas from "html2canvas" +import { RasterLayerProperties } from "../../Models/RasterLayerProperties" /** * The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties` diff --git a/UI/Map/ShowOverlayRasterLayer.ts b/UI/Map/ShowOverlayRasterLayer.ts index 5661a7bd25..30168aada4 100644 --- a/UI/Map/ShowOverlayRasterLayer.ts +++ b/UI/Map/ShowOverlayRasterLayer.ts @@ -1,8 +1,8 @@ -import { RasterLayerProperties } from "../../Models/RasterLayers" import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Map as MlMap } from "maplibre-gl" import { Utils } from "../../Utils" import { MapLibreAdaptor } from "./MapLibreAdaptor" +import { RasterLayerProperties } from "../../Models/RasterLayerProperties" export default class ShowOverlayRasterLayer { private readonly _map: UIEventSource diff --git a/assets/layoutconfigmeta.json b/assets/layoutconfigmeta.json index 2a590db976..0f15fa373b 100644 --- a/assets/layoutconfigmeta.json +++ b/assets/layoutconfigmeta.json @@ -29,24 +29,56 @@ "path": [ "title" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "The title, as shown in the welcome message and the more-screen." }, { "path": [ "shortDescription" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A short description, showed as social description and in the 'more theme'-buttons.\nNote that if this one is not defined, the first sentence of 'description' is used" }, { "path": [ "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "The description, as shown in the welcome message and the more-screen" }, { "path": [ "descriptionTail" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A part of the description, shown under the login-button." }, { @@ -107,67 +139,20 @@ "type": "array", "description": "Define some (overlay) slippy map tilesources" }, - { - "path": [ - "tileLayerSources" - ], - "type": "object", - "description": "Configuration for a tilesource config" - }, - { - "path": [ - "tileLayerSources", - "id" - ], - "type": "string", - "description": "Id of this overlay, used in the URL-parameters to set the state" - }, - { - "path": [ - "tileLayerSources", - "source" - ], - "type": "string", - "description": "The path, where {x}, {y} and {z} will be substituted" - }, - { - "path": [ - "tileLayerSources", - "isOverlay" - ], - "type": "boolean", - "description": "Wether or not this is an overlay. Default: true" - }, { "path": [ "tileLayerSources", "name" ], - "description": "How this will be shown in the selection menu.\nMake undefined if this may not be toggled" - }, - { - "path": [ - "tileLayerSources", - "minZoom" + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } ], - "type": "number", - "description": "Only visible at this or a higher zoom level" - }, - { - "path": [ - "tileLayerSources", - "maxZoom" - ], - "type": "number", - "description": "Only visible at this or a lower zoom level" - }, - { - "path": [ - "tileLayerSources", - "defaultState" - ], - "type": "boolean", - "description": "The default state, set to false to hide by default" + "description": "The name of the imagery source" }, { "path": [ @@ -193,7 +178,9 @@ } ] }, - "override": {}, + "override": { + "$ref": "#/definitions/Partial" + }, "hideTagRenderingsWithLabels": { "description": "TagRenderings with any of these labels will be removed from the layer.\nNote that the 'id' and 'group' are considered labels too", "type": "array", @@ -233,6 +220,14 @@ "layers", "name" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control" }, { @@ -240,6 +235,14 @@ "layers", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A description for this layer.\nShown in the layer selections and in the personel theme" }, { @@ -249,151 +252,58 @@ ], "type": [ { - "allOf": [ - { - "type": "object", - "properties": { - "osmTags": { - "$ref": "#/definitions/TagConfigJson", - "description": "Every source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", - "type": "number" - } - }, - "required": [ - "osmTags" - ] - }, - { - "type": "object", - "properties": { - "overpassScript": { - "description": "If set, this custom overpass-script will be used instead of building one by using the OSM-tags.\nSpecifying OSM-tags is still obligatory and will still hide non-matching items and they will be used for the rest of the pipeline.\n_This should be really rare_.\n\nFor example, when you want to fetch all grass-areas in parks and which are marked as publicly accessible:\n```\n\"source\": {\n \"overpassScript\":\n \"way[\\\"leisure\\\"=\\\"park\\\"];node(w);is_in;area._[\\\"leisure\\\"=\\\"park\\\"];(way(area)[\\\"landuse\\\"=\\\"grass\\\"]; node(w); );\",\n \"osmTags\": \"access=yes\"\n}\n```", - "type": "string" - } - } - } - ] - }, - { - "allOf": [ - { - "type": "object", - "properties": { - "osmTags": { - "$ref": "#/definitions/TagConfigJson", - "description": "Every source must set which tags have to be present in order to load the given layer." - }, - "maxCacheAge": { - "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", - "type": "number" - } - }, - "required": [ - "osmTags" - ] - }, - { - "type": "object", - "properties": { - "geoJson": { - "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}", - "type": "string" - }, - "geoJsonZoomLevel": { - "description": "To load a tiled geojson layer, set the zoomlevel of the tiles", - "type": "number" - }, - "isOsmCache": { - "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache", - "type": "boolean" - }, - "mercatorCrs": { - "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this", - "type": "boolean" - }, - "idKey": { - "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'", - "type": "string" - } - }, - "required": [ - "geoJson" - ] - } - ] - } - ], - "description": "This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.\n\nIf no 'geojson' is defined, data will be fetched from overpass and the OSM-API.\n\nEvery source _must_ define which tags _must_ be present in order to be picked up." - }, - { - "path": [ - "layers", - "source", - "osmTags" - ], - "type": [ - { - "$ref": "#/definitions/AndTagConfigJson" - }, - { - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", "type": "object", "properties": { - "or": { - "type": "array", - "items": { - "$ref": "#/definitions/TagConfigJson" - } + "osmTags": { + "$ref": "#/definitions/TagConfigJson", + "description": "Every source must set which tags have to be present in order to load the given layer." + }, + "maxCacheAge": { + "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", + "type": "number" } }, "required": [ - "or" + "osmTags" ] }, { + "type": "object", + "properties": { + "geoJson": { + "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}", + "type": "string" + }, + "geoJsonZoomLevel": { + "description": "To load a tiled geojson layer, set the zoomlevel of the tiles", + "type": "number" + }, + "isOsmCache": { + "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache", + "type": "boolean" + }, + "mercatorCrs": { + "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this", + "type": "boolean" + }, + "idKey": { + "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'", + "type": "string" + } + }, + "required": [ + "geoJson" + ] + }, + { + "enum": [ + "special", + "special:library" + ], "type": "string" } ], - "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" - }, - { - "path": [ - "layers", - "source", - "osmTags" - ], - "type": "object", - "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "source", - "osmTags" - ], - "type": "object", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "source", - "maxCacheAge" - ], - "type": "number", - "description": "The maximum amount of seconds that a tile is allowed to linger in the cache" - }, - { - "path": [ - "layers", - "source", - "overpassScript" - ], - "type": "string", - "description": "If set, this custom overpass-script will be used instead of building one by using the OSM-tags.\nSpecifying OSM-tags is still obligatory and will still hide non-matching items and they will be used for the rest of the pipeline.\n_This should be really rare_.\n\nFor example, when you want to fetch all grass-areas in parks and which are marked as publicly accessible:\n```\n\"source\": {\n \"overpassScript\":\n \"way[\\\"leisure\\\"=\\\"park\\\"];node(w);is_in;area._[\\\"leisure\\\"=\\\"park\\\"];(way(area)[\\\"landuse\\\"=\\\"grass\\\"]; node(w); );\",\n \"osmTags\": \"access=yes\"\n}\n```" + "description": "This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.\n\nIf no 'geojson' is defined, data will be fetched from overpass and the OSM-API.\n\nEvery source _must_ define which tags _must_ be present in order to be picked up.\n\nNote: a source must always be defined. 'special' is only allowed if this is a builtin-layer" }, { "path": [ @@ -703,15 +613,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "title", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -721,12 +622,39 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "title", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", "title", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -736,6 +664,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -871,6 +833,139 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "title", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "title", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "title", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "title", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "title", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "title", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "title", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "title", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -959,6 +1054,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -1068,15 +1171,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "titleIcons", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -1086,12 +1180,39 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "titleIcons", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", "titleIcons", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -1101,6 +1222,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -1236,6 +1391,139 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "titleIcons", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "titleIcons", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "titleIcons", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "titleIcons", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "titleIcons", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "titleIcons", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "titleIcons", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "titleIcons", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -1324,6 +1612,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -1423,7 +1719,7 @@ "location" ], "type": "array", - "description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint.\n'projected_centerpoint' will show an item on the line itself, near the middle of the line. (LineStrings only)" + "description": "All the locations that this point should be rendered at.\nPossible values are:\n- `point`: only renders points at their location\n- `centroid`: show a symbol at the centerpoint of a (multi)Linestring and (multi)polygon. Points will _not_ be rendered with this\n- `projected_centerpoint`: Only on (multi)linestrings: calculate the centerpoint and snap it to the way\n- `start` and `end`: only on linestrings: add a point to the first/last coordinate of the LineString" }, { "path": [ @@ -1461,16 +1757,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "icon", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -1481,6 +1767,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "icon", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -1488,6 +1794,14 @@ "icon", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -1498,6 +1812,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -1641,6 +1989,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "icon", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "icon", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "icon", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "icon", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "icon", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "icon", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "icon", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "icon", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -1736,6 +2225,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -1895,17 +2392,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "iconBadges", - "then", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -1917,6 +2403,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "iconBadges", + "then", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -1925,6 +2432,14 @@ "then", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -1936,6 +2451,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -2087,6 +2636,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "iconBadges", + "then", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "iconBadges", + "then", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "iconBadges", + "then", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "iconBadges", + "then", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "iconBadges", + "then", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -2189,6 +2887,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -2286,16 +2992,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "iconSize", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -2306,6 +3002,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "iconSize", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -2313,6 +3029,14 @@ "iconSize", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -2323,6 +3047,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -2466,6 +3224,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "iconSize", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "iconSize", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "iconSize", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "iconSize", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "iconSize", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "iconSize", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "iconSize", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "iconSize", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -2561,6 +3460,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -2655,16 +3562,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "rotation", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -2675,6 +3572,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "rotation", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -2682,6 +3599,14 @@ "rotation", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -2692,6 +3617,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -2835,6 +3794,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "rotation", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "rotation", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotation", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotation", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotation", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotation", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotation", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotation", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -2930,6 +4030,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -3024,16 +4132,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "label", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -3044,6 +4142,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "label", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -3051,6 +4169,14 @@ "label", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -3061,6 +4187,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -3204,6 +4364,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "label", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "label", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "label", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "label", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "label", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "label", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "label", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "label", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -3299,6 +4600,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -3372,7 +4681,7 @@ "type": "string" } ], - "description": "A snippet of css code" + "description": "A snippet of css code which is applied onto the container of the entire marker" }, { "path": [ @@ -3393,16 +4702,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "css", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -3413,6 +4712,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "css", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -3420,6 +4739,14 @@ "css", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -3430,6 +4757,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -3573,6 +4934,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "css", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -3668,6 +5170,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -3741,7 +5251,7 @@ "type": "string" } ], - "description": "A snippet of css-classes. They can be space-separated" + "description": "A snippet of css-classes which are applied onto the container of the entire marker. They can be space-separated" }, { "path": [ @@ -3762,16 +5272,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "cssClasses", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -3782,6 +5282,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -3789,6 +5309,14 @@ "cssClasses", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -3799,6 +5327,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -3942,6 +5504,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -4037,6 +5740,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -4096,6 +5807,2294 @@ "type": "string", "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" }, + { + "path": [ + "layers", + "mapRendering", + "labelCss" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Css that is applied onto the label" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCss", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Css classes that are applied onto the label; can be space-separated" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "labelCssClasses", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ], + "description": "If the map is pitched, the marker will stay parallel to the screen.\nSet to 'map' if you want to put it flattened on the map" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "pitchAlignment", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ], + "description": "If the map is rotated, the icon will still point to the north if no rotation was applied" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "rotationAlignment", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, { "path": [ "layers", @@ -4139,16 +8138,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "color", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -4159,6 +8148,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "color", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -4166,6 +8175,14 @@ "color", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -4176,6 +8193,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -4319,6 +8370,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "color", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "color", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "color", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "color", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "color", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "color", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "color", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "color", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -4414,6 +8606,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -4511,16 +8711,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "width", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -4531,6 +8721,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "width", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -4538,6 +8748,14 @@ "width", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -4548,6 +8766,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -4691,6 +8943,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "width", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "width", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "width", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "width", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "width", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "width", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "width", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "width", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -4786,6 +9179,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -4880,16 +9281,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "dashArray", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -4900,6 +9291,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "dashArray", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -4907,6 +9318,14 @@ "dashArray", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -4917,6 +9336,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -5060,6 +9513,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "dashArray", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "dashArray", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "dashArray", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "dashArray", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "dashArray", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "dashArray", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "dashArray", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "dashArray", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -5155,6 +9749,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -5249,16 +9851,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "lineCap", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -5269,6 +9861,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "lineCap", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -5276,6 +9888,14 @@ "lineCap", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -5286,6 +9906,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -5429,6 +10083,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "lineCap", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "lineCap", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "lineCap", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "lineCap", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "lineCap", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "lineCap", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "lineCap", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "lineCap", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -5524,6 +10319,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -5622,16 +10425,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "fill", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -5642,6 +10435,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "fill", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -5649,6 +10462,14 @@ "fill", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -5659,6 +10480,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -5802,6 +10657,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "fill", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "fill", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "fill", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "fill", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "fill", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "fill", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "fill", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "fill", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -5897,6 +10893,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -5991,16 +10995,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "fillColor", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -6011,6 +11005,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "fillColor", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -6018,6 +11032,14 @@ "fillColor", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -6028,6 +11050,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -6171,6 +11227,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "fillColor", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "fillColor", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "fillColor", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "fillColor", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "fillColor", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "fillColor", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "fillColor", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "fillColor", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -6266,6 +11463,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -6360,16 +11565,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "offset", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -6380,6 +11575,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "offset", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -6387,6 +11602,14 @@ "offset", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -6397,6 +11620,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -6540,6 +11797,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "offset", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "offset", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "offset", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "offset", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "offset", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "offset", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "offset", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "offset", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -6635,6 +12033,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -6719,7 +12125,7 @@ "location" ], "type": "array", - "description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint.\n'projected_centerpoint' will show an item on the line itself, near the middle of the line. (LineStrings only)" + "description": "All the locations that this point should be rendered at.\nPossible values are:\n- `point`: only renders points at their location\n- `centroid`: show a symbol at the centerpoint of a (multi)Linestring and (multi)polygon. Points will _not_ be rendered with this\n- `projected_centerpoint`: Only on (multi)linestrings: calculate the centerpoint and snap it to the way\n- `start` and `end`: only on linestrings: add a point to the first/last coordinate of the LineString" }, { "path": [ @@ -6760,17 +12166,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "icon", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -6782,6 +12177,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -6790,6 +12206,14 @@ "icon", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -6801,6 +12225,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -6952,6 +12410,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -7054,6 +12661,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -7223,18 +12838,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "iconBadges", - "then", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -7247,6 +12850,28 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -7256,6 +12881,14 @@ "then", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -7268,6 +12901,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -7427,6 +13094,163 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -7536,6 +13360,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -7639,17 +13471,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "iconSize", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -7661,6 +13482,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -7669,6 +13511,14 @@ "iconSize", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -7680,6 +13530,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -7831,6 +13715,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -7933,6 +13966,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -8033,17 +14074,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "rotation", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -8055,6 +14085,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -8063,6 +14114,14 @@ "rotation", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -8074,6 +14133,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -8225,6 +14318,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -8327,6 +14569,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -8427,17 +14677,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "label", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -8449,6 +14688,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -8457,6 +14717,14 @@ "label", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -8468,6 +14736,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -8619,6 +14921,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -8721,6 +15172,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -8798,7 +15257,7 @@ "type": "string" } ], - "description": "A snippet of css code" + "description": "A snippet of css code which is applied onto the container of the entire marker" }, { "path": [ @@ -8821,17 +15280,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "css", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -8843,6 +15291,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -8851,6 +15320,14 @@ "css", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -8862,6 +15339,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -9013,6 +15524,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -9115,6 +15775,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -9192,7 +15860,7 @@ "type": "string" } ], - "description": "A snippet of css-classes. They can be space-separated" + "description": "A snippet of css-classes which are applied onto the container of the entire marker. They can be space-separated" }, { "path": [ @@ -9215,17 +15883,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "cssClasses", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -9237,6 +15894,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -9245,6 +15923,14 @@ "cssClasses", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -9256,6 +15942,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -9407,6 +16127,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -9509,6 +16378,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -9571,6 +16448,2426 @@ "type": "string", "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Css that is applied onto the label" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Css classes that are applied onto the label; can be space-separated" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ], + "description": "If the map is pitched, the marker will stay parallel to the screen.\nSet to 'map' if you want to put it flattened on the map" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ], + "description": "If the map is rotated, the icon will still point to the north if no rotation was applied" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, { "path": [ "layers", @@ -9618,17 +18915,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "color", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -9640,6 +18926,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -9648,6 +18955,14 @@ "color", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -9659,6 +18974,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -9810,6 +19159,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -9912,6 +19410,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -10015,17 +19521,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "width", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -10037,6 +19532,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -10045,6 +19561,14 @@ "width", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -10056,6 +19580,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -10207,6 +19765,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -10309,6 +20016,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -10409,17 +20124,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "dashArray", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -10431,6 +20135,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -10439,6 +20164,14 @@ "dashArray", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -10450,6 +20183,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -10601,6 +20368,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -10703,6 +20619,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -10803,17 +20727,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "lineCap", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -10825,6 +20738,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -10833,6 +20767,14 @@ "lineCap", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -10844,6 +20786,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -10995,6 +20971,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -11097,6 +21222,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -11201,17 +21334,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "fill", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -11223,6 +21345,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -11231,6 +21374,14 @@ "fill", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -11242,6 +21393,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -11393,6 +21578,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -11495,6 +21829,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -11595,17 +21937,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "fillColor", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -11617,6 +21948,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -11625,6 +21977,14 @@ "fillColor", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -11636,6 +21996,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -11787,6 +22181,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -11889,6 +22432,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -11989,17 +22540,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "offset", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -12011,6 +22551,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -12019,6 +22580,14 @@ "offset", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -12030,6 +22599,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -12181,6 +22784,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -12283,6 +23035,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -12392,17 +23152,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "color", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -12414,6 +23163,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -12422,6 +23192,14 @@ "color", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -12433,6 +23211,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -12584,6 +23396,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "color", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -12686,6 +23647,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -12789,17 +23758,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "width", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -12811,6 +23769,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -12819,6 +23798,14 @@ "width", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -12830,6 +23817,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -12981,6 +24002,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "width", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -13083,6 +24253,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -13183,17 +24361,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "dashArray", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -13205,6 +24372,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -13213,6 +24401,14 @@ "dashArray", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -13224,6 +24420,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -13375,6 +24605,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -13477,6 +24856,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -13577,17 +24964,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "lineCap", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -13599,6 +24975,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -13607,6 +25004,14 @@ "lineCap", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -13618,6 +25023,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -13769,6 +25208,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -13871,6 +25459,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -13975,17 +25571,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "fill", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -13997,6 +25582,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -14005,6 +25611,14 @@ "fill", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -14016,6 +25630,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -14167,6 +25815,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fill", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -14269,6 +26066,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -14369,17 +26174,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "fillColor", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -14391,6 +26185,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -14399,6 +26214,14 @@ "fillColor", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -14410,6 +26233,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -14561,6 +26418,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -14663,6 +26669,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -14763,17 +26777,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "offset", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -14785,6 +26788,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -14793,6 +26817,14 @@ "offset", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -14804,6 +26836,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -14955,6 +27021,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "offset", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -15057,6 +27272,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -15136,7 +27359,7 @@ "location" ], "type": "array", - "description": "All the locations that this point should be rendered at.\nUsing `location: [\"point\", \"centroid\"] will always render centerpoint.\n'projected_centerpoint' will show an item on the line itself, near the middle of the line. (LineStrings only)" + "description": "All the locations that this point should be rendered at.\nPossible values are:\n- `point`: only renders points at their location\n- `centroid`: show a symbol at the centerpoint of a (multi)Linestring and (multi)polygon. Points will _not_ be rendered with this\n- `projected_centerpoint`: Only on (multi)linestrings: calculate the centerpoint and snap it to the way\n- `start` and `end`: only on linestrings: add a point to the first/last coordinate of the LineString" }, { "path": [ @@ -15177,17 +27400,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "icon", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -15199,6 +27411,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -15207,6 +27440,14 @@ "icon", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -15218,6 +27459,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -15369,6 +27644,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "icon", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -15471,6 +27895,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -15640,18 +28072,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "iconBadges", - "then", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -15664,6 +28084,28 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -15673,6 +28115,14 @@ "then", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -15685,6 +28135,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -15844,6 +28328,163 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -15953,6 +28594,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -16056,17 +28705,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "iconSize", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -16078,6 +28716,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -16086,6 +28745,14 @@ "iconSize", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -16097,6 +28764,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -16248,6 +28949,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -16350,6 +29200,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -16450,17 +29308,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "rotation", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -16472,6 +29319,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -16480,6 +29348,14 @@ "rotation", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -16491,6 +29367,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -16642,6 +29552,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -16744,6 +29803,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -16844,17 +29911,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "label", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -16866,6 +29922,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -16874,6 +29951,14 @@ "label", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -16885,6 +29970,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -17036,6 +30155,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "label", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -17138,6 +30406,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -17215,7 +30491,7 @@ "type": "string" } ], - "description": "A snippet of css code" + "description": "A snippet of css code which is applied onto the container of the entire marker" }, { "path": [ @@ -17238,17 +30514,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "css", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -17260,6 +30525,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -17268,6 +30554,14 @@ "css", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -17279,6 +30573,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -17430,6 +30758,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -17532,6 +31009,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -17609,7 +31094,7 @@ "type": "string" } ], - "description": "A snippet of css-classes. They can be space-separated" + "description": "A snippet of css-classes which are applied onto the container of the entire marker. They can be space-separated" }, { "path": [ @@ -17632,17 +31117,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "mapRendering", - "renderings", - "cssClasses", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -17654,6 +31128,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -17662,6 +31157,14 @@ "cssClasses", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -17673,6 +31176,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -17824,6 +31361,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -17926,6 +31612,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { @@ -17988,6 +31682,2426 @@ "type": "string", "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Css that is applied onto the label" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Css classes that are applied onto the label; can be space-separated" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ], + "description": "If the map is pitched, the marker will stay parallel to the screen.\nSet to 'map' if you want to put it flattened on the map" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ], + "description": "If the map is rotated, the icon will still point to the north if no rotation was applied" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, { "path": [ "layers", @@ -18010,6 +34124,14 @@ "presets", "title" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "The title - shown on the 'add-new'-button.\n\nThis should include the article of the noun, e.g. 'a hydrant', 'a bicycle pump'.\nThis text will be inserted into `Add {category} here`, becoming `Add a hydrant here`.\n\nDo _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!" }, { @@ -18027,6 +34149,14 @@ "presets", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)" }, { @@ -18154,6 +34284,9 @@ { "type": "object", "properties": { + "id": { + "type": "string" + }, "builtin": { "anyOf": [ { @@ -18176,28 +34309,6 @@ "override" ] }, - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "builtin": { - "type": "array", - "items": { - "type": "string" - } - }, - "override": { - "$ref": "#/definitions/Partial" - } - }, - "required": [ - "builtin", - "id", - "override" - ] - }, { "allOf": [ { @@ -18318,7 +34429,7 @@ "inline" ], "type": "boolean", - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout." + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present." }, { "path": [ @@ -18919,15 +35030,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "tagRenderings", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -18937,12 +35039,39 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "tagRenderings", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", "tagRenderings", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -18952,6 +35081,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -19087,6 +35250,139 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "tagRenderings", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "tagRenderings", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -19183,7 +35479,7 @@ "inline" ], "type": "boolean", - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout." + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present." }, { "path": [ @@ -19821,16 +36117,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -19841,6 +36127,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "tagRenderings", + "override", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -19848,6 +36154,14 @@ "override", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -19858,6 +36172,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -20006,412 +36354,7 @@ "layers", "tagRenderings", "override", - "question" - ], - "type": [ - { - "$ref": "#/definitions/Record" - }, - { - "type": "string" - } - ], - "description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "questionHint" - ], - "type": [ - { - "$ref": "#/definitions/Record" - }, - { - "type": "string" - } - ], - "description": "A hint which is shown in subtle text under the question.\nThis can give some extra information on what the answer should ook like" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "freeform" - ], - "type": "object", - "description": "Allow freeform text input from the user" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "freeform", - "type" - ], - "type": "string", - "description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "freeform", - "placeholder" - ], - "description": "A (translated) text that is shown (as gray text) within the textfield" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "freeform", - "helperArgs" - ], - "type": "array", - "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "freeform", - "addExtraTags" - ], - "type": "array", - "description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "freeform", - "inline" - ], - "type": "boolean", - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout." - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "freeform", - "default" - ], - "type": "string", - "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "multiAnswer" - ], - "type": "boolean", - "description": "If true, use checkboxes instead of radio buttons when asking the question" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings" - ], - "type": "array", - "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "if" - ], - "type": [ - { - "$ref": "#/definitions/AndTagConfigJson" - }, - { - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", - "type": "object", - "properties": { - "or": { - "type": "array", - "items": { - "$ref": "#/definitions/TagConfigJson" - } - } - }, - "required": [ - "or" - ] - }, - { - "type": "string" - } - ], - "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "if" - ], - "type": "object", - "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "if" - ], - "type": "object", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "then" - ], - "typeHint": "rendered", - "description": "Shown if the 'if is fulfilled\nType: rendered" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "icon" - ], - "typeHint": "icon", - "type": [ - { - "type": "object", - "properties": { - "path": { - "description": "The path to the icon\nType: icon", - "type": "string" - }, - "class": { - "description": "Size of the image", - "type": "string" - } - }, - "required": [ - "path" - ] - }, - { - "type": "string" - } - ], - "description": "An extra icon supporting the choice\nType: icon" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "icon", - "path" - ], - "typeHint": "icon", - "type": "string", - "description": "The path to the icon\nType: icon" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "icon", - "class" - ], - "type": "string", - "description": "Size of the image" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "hideInAnswer" - ], - "type": [ - { - "$ref": "#/definitions/AndTagConfigJson", - "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "$ref": "#/definitions/OrTagConfigJson", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "type": [ - "string", - "boolean" - ] - } - ], - "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n\n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "hideInAnswer" - ], - "type": "object", - "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "hideInAnswer", - "and" - ], - "type": [ - { - "$ref": "#/definitions/AndTagConfigJson" - }, - { - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", - "type": "object", - "properties": { - "or": { - "type": "array", - "items": { - "$ref": "#/definitions/TagConfigJson" - } - } - }, - "required": [ - "or" - ] - }, - { - "type": "string" - } - ], - "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "hideInAnswer", - "and" - ], - "type": "object", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "hideInAnswer" - ], - "type": "object", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "hideInAnswer", - "or" - ], - "type": [ - { - "$ref": "#/definitions/AndTagConfigJson" - }, - { - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", - "type": "object", - "properties": { - "or": { - "type": "array", - "items": { - "$ref": "#/definitions/TagConfigJson" - } - } - }, - "required": [ - "or" - ] - }, - { - "type": "string" - } - ], - "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "hideInAnswer", - "or" - ], - "type": "object", - "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "hideInAnswer", - "or" - ], - "type": "object", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "ifnot" + "metacondition" ], "type": [ { @@ -20426,15 +36369,14 @@ "type": "string" } ], - "description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`" + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" }, { "path": [ "layers", "tagRenderings", "override", - "mappings", - "ifnot" + "metacondition" ], "type": "object", "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" @@ -20444,8 +36386,7 @@ "layers", "tagRenderings", "override", - "mappings", - "ifnot", + "metacondition", "and" ], "type": [ @@ -20478,8 +36419,7 @@ "layers", "tagRenderings", "override", - "mappings", - "ifnot", + "metacondition", "and" ], "type": "object", @@ -20490,8 +36430,7 @@ "layers", "tagRenderings", "override", - "mappings", - "ifnot" + "metacondition" ], "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" @@ -20501,8 +36440,7 @@ "layers", "tagRenderings", "override", - "mappings", - "ifnot", + "metacondition", "or" ], "type": [ @@ -20535,8 +36473,7 @@ "layers", "tagRenderings", "override", - "mappings", - "ifnot", + "metacondition", "or" ], "type": "object", @@ -20547,369 +36484,7 @@ "layers", "tagRenderings", "override", - "mappings", - "ifnot", - "or" - ], - "type": "object", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "addExtraTags" - ], - "type": "array", - "description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer.\n\nThis can be used e.g. to erase other keys which indicate the 'not' value:\n```json\n{\n \"if\": \"crossing:marking=rainbow\",\n \"then\": \"This is a rainbow crossing\",\n \"addExtraTags\": \"not:crossing:marking=\"\n}\n```" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "priorityIf" - ], - "type": [ - { - "$ref": "#/definitions/AndTagConfigJson", - "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "$ref": "#/definitions/OrTagConfigJson", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "type": "string" - } - ], - "description": "If the searchable selector is picked, mappings with this item will have priority and show up even if the others are hidden\nUse this sparingly" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "priorityIf" - ], - "type": "object", - "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "priorityIf", - "and" - ], - "type": [ - { - "$ref": "#/definitions/AndTagConfigJson" - }, - { - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", - "type": "object", - "properties": { - "or": { - "type": "array", - "items": { - "$ref": "#/definitions/TagConfigJson" - } - } - }, - "required": [ - "or" - ] - }, - { - "type": "string" - } - ], - "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "priorityIf", - "and" - ], - "type": "object", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "priorityIf" - ], - "type": "object", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "priorityIf", - "or" - ], - "type": [ - { - "$ref": "#/definitions/AndTagConfigJson" - }, - { - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", - "type": "object", - "properties": { - "or": { - "type": "array", - "items": { - "$ref": "#/definitions/TagConfigJson" - } - } - }, - "required": [ - "or" - ] - }, - { - "type": "string" - } - ], - "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "priorityIf", - "or" - ], - "type": "object", - "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "priorityIf", - "or" - ], - "type": "object", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "mappings", - "#" - ], - "type": "string", - "description": "Used for comments or to disable a validation\n\nignore-image-in-then: normally, a `then`-clause is not allowed to have an `img`-html-element as icons are preferred. In some cases (most notably title-icons), this is allowed" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "id" - ], - "type": "string", - "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "labels" - ], - "type": "array", - "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "description" - ], - "description": "A human-readable text explaining what this tagRendering does" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "render" - ], - "typeHint": "rendered", - "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "condition" - ], - "type": [ - { - "$ref": "#/definitions/AndTagConfigJson", - "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "$ref": "#/definitions/OrTagConfigJson", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "type": "string" - } - ], - "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "condition" - ], - "type": "object", - "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "condition", - "and" - ], - "type": [ - { - "$ref": "#/definitions/AndTagConfigJson" - }, - { - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", - "type": "object", - "properties": { - "or": { - "type": "array", - "items": { - "$ref": "#/definitions/TagConfigJson" - } - } - }, - "required": [ - "or" - ] - }, - { - "type": "string" - } - ], - "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "condition", - "and" - ], - "type": "object", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "condition" - ], - "type": "object", - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "condition", - "or" - ], - "type": [ - { - "$ref": "#/definitions/AndTagConfigJson" - }, - { - "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", - "type": "object", - "properties": { - "or": { - "type": "array", - "items": { - "$ref": "#/definitions/TagConfigJson" - } - } - }, - "required": [ - "or" - ] - }, - { - "type": "string" - } - ], - "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "condition", - "or" - ], - "type": "object", - "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" - }, - { - "path": [ - "layers", - "tagRenderings", - "override", - "condition", + "metacondition", "or" ], "type": "object", @@ -21028,7 +36603,7 @@ "inline" ], "type": "boolean", - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout." + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present." }, { "path": [ @@ -21666,16 +37241,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "tagRenderings", - "renderings", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -21686,6 +37251,26 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -21693,6 +37278,14 @@ "renderings", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -21703,6 +37296,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -21846,6 +37473,147 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -21950,7 +37718,7 @@ "inline" ], "type": "boolean", - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout." + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present." }, { "path": [ @@ -22625,17 +38393,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "layers", - "tagRenderings", - "renderings", - "override", - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "layers", @@ -22647,6 +38404,27 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "override", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "layers", @@ -22655,6 +38433,14 @@ "override", "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -22666,6 +38452,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -22817,6 +38637,155 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "override", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "override", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "override", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "override", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "override", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "override", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "override", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "tagRenderings", + "renderings", + "override", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "layers", @@ -23491,53 +39460,41482 @@ { "path": [ "layers", - "hideTagRenderingsWithLabels" + "override", + "id" ], - "type": "array", - "description": "TagRenderings with any of these labels will be removed from the layer.\nNote that the 'id' and 'group' are considered labels too" + "type": "string", + "description": "The id of this layer.\nThis should be a simple, lowercase, human readable string that is used to identify the layer." }, { "path": [ - "clustering" + "layers", + "override", + "name" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "The name of this layer\nUsed in the layer control panel and the 'Personal theme'.\n\nIf not given, will be hidden (and thus not toggable) in the layer control" + }, + { + "path": [ + "layers", + "override", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A description for this layer.\nShown in the layer selections and in the personel theme" + }, + { + "path": [ + "layers", + "override", + "source" ], "type": [ { "type": "object", "properties": { - "maxZoom": { - "description": "All zoom levels above 'maxzoom' are not clustered anymore.\nDefaults to 18", + "osmTags": { + "$ref": "#/definitions/TagConfigJson", + "description": "Every source must set which tags have to be present in order to load the given layer." + }, + "maxCacheAge": { + "description": "The maximum amount of seconds that a tile is allowed to linger in the cache", + "type": "number" + } + }, + "required": [ + "osmTags" + ] + }, + { + "type": "object", + "properties": { + "geoJson": { + "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}", + "type": "string" + }, + "geoJsonZoomLevel": { + "description": "To load a tiled geojson layer, set the zoomlevel of the tiles", "type": "number" }, - "minNeededElements": { - "description": "The number of elements per tile needed to start clustering\nIf clustering is defined, defaults to 250", + "isOsmCache": { + "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache", + "type": "boolean" + }, + "mercatorCrs": { + "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this", + "type": "boolean" + }, + "idKey": { + "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'", + "type": "string" + } + }, + "required": [ + "geoJson" + ] + }, + { + "enum": [ + "special", + "special:library" + ], + "type": "string" + } + ], + "description": "This determines where the data for the layer is fetched: from OSM or from an external geojson dataset.\n\nIf no 'geojson' is defined, data will be fetched from overpass and the OSM-API.\n\nEvery source _must_ define which tags _must_ be present in order to be picked up.\n\nNote: a source must always be defined. 'special' is only allowed if this is a builtin-layer" + }, + { + "path": [ + "layers", + "override", + "source", + "osmTags" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "source", + "osmTags" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "source", + "osmTags" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "source", + "maxCacheAge" + ], + "type": "number", + "description": "The maximum amount of seconds that a tile is allowed to linger in the cache" + }, + { + "path": [ + "layers", + "override", + "source", + "geoJson" + ], + "type": "string", + "description": "The actual source of the data to load, if loaded via geojson.\n\n# A single geojson-file\nsource: {geoJson: \"https://my.source.net/some-geo-data.geojson\"}\n fetches a geojson from a third party source\n\n# A tiled geojson source\nsource: {geoJson: \"https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson\", geoJsonZoomLevel: 14}\n to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer\n\nSome API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}" + }, + { + "path": [ + "layers", + "override", + "source", + "geoJsonZoomLevel" + ], + "type": "number", + "description": "To load a tiled geojson layer, set the zoomlevel of the tiles" + }, + { + "path": [ + "layers", + "override", + "source", + "isOsmCache" + ], + "type": "boolean", + "description": "Indicates that the upstream geojson data is OSM-derived.\nUseful for e.g. merging or for scripts generating this cache" + }, + { + "path": [ + "layers", + "override", + "source", + "mercatorCrs" + ], + "type": "boolean", + "description": "Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this" + }, + { + "path": [ + "layers", + "override", + "source", + "idKey" + ], + "type": "string", + "description": "Some API's have an id-field, but give it a different name.\nSetting this key will rename this field into 'id'" + }, + { + "path": [ + "layers", + "override", + "calculatedTags" + ], + "type": "array", + "description": "A list of extra tags to calculate, specified as \"keyToAssignTo=javascript-expression\".\nThere are a few extra functions available. Refer to Docs/CalculatedTags.md for more information\nThe functions will be run in order, e.g.\n[\n \"_max_overlap_m2=Math.max(...feat.overlapsWith(\"someOtherLayer\").map(o => o.overlap))\n \"_max_overlap_ratio=Number(feat._max_overlap_m2)/feat.area\n]\n\nThe specified tags are evaluated lazily. E.g. if a calculated tag is only used in the popup (e.g. the number of nearby features),\nthe expensive calculation will only be performed then for that feature. This avoids clogging up the contributors PC when all features are loaded.\n\nIf a tag has to be evaluated strictly, use ':=' instead:\n\n[\n\"_some_key:=some_javascript_expression\"\n]" + }, + { + "path": [ + "layers", + "override", + "doNotDownload" + ], + "type": "boolean", + "description": "If set, this layer will not query overpass; but it'll still match the tags above which are by chance returned by other layers.\nWorks well together with 'passAllFeatures', to add decoration" + }, + { + "path": [ + "layers", + "override", + "isShown" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, only features matching this extra tag will be shown.\nThis is useful to hide certain features from view.\n\nImportant: hiding features does not work dynamically, but is only calculated when the data is first renders.\nThis implies that it is not possible to hide a feature after a tagging change\n\nThe default value is 'yes'" + }, + { + "path": [ + "layers", + "override", + "isShown" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "isShown", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "isShown", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "isShown" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "isShown", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "isShown", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "isShown", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "forceLoad" + ], + "type": "boolean", + "description": "Advanced option - might be set by the theme compiler\n\nIf true, this data will _always_ be loaded, even if the theme is disabled" + }, + { + "path": [ + "layers", + "override", + "minzoom" + ], + "type": "number", + "description": "The minimum needed zoomlevel required before loading of the data start\nDefault: 0" + }, + { + "path": [ + "layers", + "override", + "shownByDefault" + ], + "type": "boolean", + "description": "Indicates if this layer is shown by default;\ncan be used to hide a layer from start, or to load the layer but only to show it where appropriate (e.g. for snapping to it)" + }, + { + "path": [ + "layers", + "override", + "minzoomVisible" + ], + "type": "number", + "description": "The zoom level at which point the data is hidden again\nDefault: 100 (thus: always visible" + }, + { + "path": [ + "layers", + "override", + "title" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The title shown in a popup for elements of this layer." + }, + { + "path": [ + "layers", + "override", + "title" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "title", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "title", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "title", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "title", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "title", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "title", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "title", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "title", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "title", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "title", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "title", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "title", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "title", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "title", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "title", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "titleIcons" + ], + "typeHint": "icon[]", + "type": [ + { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ] + } + }, + { + "type": "array", + "items": [ + { + "type": "string", + "enum": [ + "defaults" + ] + } + ], + "minItems": 1, + "maxItems": 1 + } + ], + "description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\n\nType: icon[]" + }, + { + "path": [ + "layers", + "override", + "titleIcons" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "titleIcons", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering" + ], + "type": [ + { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/default_4" + }, + { + "$ref": "#/definitions/default_5" + }, + { + "$ref": "#/definitions/default" + } + ] + } + }, + { + "type": "null" + } + ], + "description": "Visualisation of the items on the map" + }, + { + "path": [ + "layers", + "override", + "mapRendering" + ], + "type": "object", + "description": "The PointRenderingConfig gives all details onto how to render a single point of a feature.\n\nThis can be used if:\n\n- The feature is a point\n- To render something at the centroid of an area, or at the start, end or projected centroid of a way" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "location" + ], + "type": "array", + "description": "All the locations that this point should be rendered at.\nPossible values are:\n- `point`: only renders points at their location\n- `centroid`: show a symbol at the centerpoint of a (multi)Linestring and (multi)polygon. Points will _not_ be rendered with this\n- `projected_centerpoint`: Only on (multi)linestrings: calculate the centerpoint and snap it to the way\n- `start` and `end`: only on linestrings: add a point to the first/last coordinate of the LineString" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The icon for an element.\nNote that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets.\n\nThe result of the icon is rendered as follows:\nthe resulting string is interpreted as a _list_ of items, separated by \";\". The bottommost layer is the first layer.\nAs a result, on could use a generic pin, then overlay it with a specific icon.\nTo make things even more practical, one can use all SVG's from the folder \"assets/svg\" and _substitute the color_ in it.\nE.g. to draw a red pin, use \"pin:#f00\", to have a green circle with your icon on top, use `circle:#0f0;`\n\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "icon", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges" + ], + "type": "array", + "description": "A list of extra badges to show next to the icon as small badge\nThey will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout.\n\nNote: strings are interpreted as icons, so layering and substituting is supported. You can use `circle:white;./my_icon.svg` to add a background circle" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then" + ], + "typeHint": "icon", + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Badge to show\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconBadges", + "then", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A string containing \"width,height\" or \"width,height,anchorpoint\" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ...\nDefault is '40,40,center'" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "iconSize", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The rotation of an icon, useful for e.g. directions.\nUsage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)``" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotation", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A HTML-fragment that is shown below the icon, for example:\n
{name}
\n\nIf the icon is undefined, then the label is shown in the center of the feature.\nNote that, if the wayhandling hides the icon then no label is shown as well." + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "label", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A snippet of css code which is applied onto the container of the entire marker" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "css", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A snippet of css-classes which are applied onto the container of the entire marker. They can be space-separated" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "cssClasses", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Css that is applied onto the label" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCss", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Css classes that are applied onto the label; can be space-separated" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "labelCssClasses", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ], + "description": "If the map is pitched, the marker will stay parallel to the screen.\nSet to 'map' if you want to put it flattened on the map" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "pitchAlignment", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ], + "description": "If the map is rotated, the icon will still point to the north if no rotation was applied" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "rotationAlignment", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering" + ], + "type": "object", + "description": "The LineRenderingConfig gives all details onto how to render a single line of a feature.\n\nThis can be used if:\n\n- The feature is a line\n- The feature is an area" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The color for way-elements and SVG-elements.\nIf the value starts with \"--\", the style of the body element will be queried for the corresponding variable instead" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "color", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": [ + "string", + "number" + ] + } + ], + "description": "The stroke-width for way-elements" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "width", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A dasharray, e.g. \"5 6\"\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap',\nDefault value: \"\" (empty string == full line)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "dashArray", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The form at the end of a line" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "lineCap", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "no", + "yes" + ], + "type": "string" + } + ], + "description": "Whether or not to fill polygons" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fill", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The color to fill a polygon with.\nIf undefined, this will be slightly more opaque version of the stroke line" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "fillColor", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "number" + } + ], + "description": "The number of pixels this line should be moved.\nUse a positive numbe to move to the right, a negative to move to the left (left/right as defined by the drawing direction of the line).\n\nIMPORTANT: MapComplete will already normalize 'key:both:property' and 'key:both' into the corresponding 'key:left' and 'key:right' tagging (same for 'sidewalk=left/right/both' which is rewritten to 'sidewalk:left' and 'sidewalk:right')\nThis simplifies programming. Refer to the CalculatedTags.md-documentation for more details" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "offset", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering" + ], + "type": "object", + "description": "Rewrites and multiplies the given renderings of type T.\n\nThis can be used for introducing many similar questions automatically,\nwhich also makes translations easier.\n\n(Note that the key does _not_ need to be wrapped in {}.\nHowever, we recommend to use them if the key is used in a translation, as missing keys will be picked up and warned for by the translation scripts)\n\nFor example:\n\n```\n{\n rewrite: {\n sourceString: [\"key\", \"a|b|c\"],\n into: [\n [\"X\", 0]\n [\"Y\", 1],\n [\"Z\", 2]\n ],\n renderings: [{\n \"key\":\"a|b|c\"\n }]\n }\n}\n```\nwill result in _three_ copies (as the values to rewrite into have three values, namely:\n\n[\n {\n # The first pair: key --> X, a|b|c --> 0\n \"X\": 0\n },\n {\n \"Y\": 1\n },\n {\n \"Z\": 2\n }\n\n]" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings" + ], + "type": "object", + "description": "The PointRenderingConfig gives all details onto how to render a single point of a feature.\n\nThis can be used if:\n\n- The feature is a point\n- To render something at the centroid of an area, or at the start, end or projected centroid of a way" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "location" + ], + "type": "array", + "description": "All the locations that this point should be rendered at.\nPossible values are:\n- `point`: only renders points at their location\n- `centroid`: show a symbol at the centerpoint of a (multi)Linestring and (multi)polygon. Points will _not_ be rendered with this\n- `projected_centerpoint`: Only on (multi)linestrings: calculate the centerpoint and snap it to the way\n- `start` and `end`: only on linestrings: add a point to the first/last coordinate of the LineString" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The icon for an element.\nNote that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets.\n\nThe result of the icon is rendered as follows:\nthe resulting string is interpreted as a _list_ of items, separated by \";\". The bottommost layer is the first layer.\nAs a result, on could use a generic pin, then overlay it with a specific icon.\nTo make things even more practical, one can use all SVG's from the folder \"assets/svg\" and _substitute the color_ in it.\nE.g. to draw a red pin, use \"pin:#f00\", to have a green circle with your icon on top, use `circle:#0f0;`\n\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges" + ], + "type": "array", + "description": "A list of extra badges to show next to the icon as small badge\nThey will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout.\n\nNote: strings are interpreted as icons, so layering and substituting is supported. You can use `circle:white;./my_icon.svg` to add a background circle" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then" + ], + "typeHint": "icon", + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Badge to show\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A string containing \"width,height\" or \"width,height,anchorpoint\" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ...\nDefault is '40,40,center'" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The rotation of an icon, useful for e.g. directions.\nUsage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)``" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A HTML-fragment that is shown below the icon, for example:\n
{name}
\n\nIf the icon is undefined, then the label is shown in the center of the feature.\nNote that, if the wayhandling hides the icon then no label is shown as well." + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A snippet of css code which is applied onto the container of the entire marker" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A snippet of css-classes which are applied onto the container of the entire marker. They can be space-separated" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Css that is applied onto the label" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Css classes that are applied onto the label; can be space-separated" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ], + "description": "If the map is pitched, the marker will stay parallel to the screen.\nSet to 'map' if you want to put it flattened on the map" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ], + "description": "If the map is rotated, the icon will still point to the north if no rotation was applied" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings" + ], + "type": "object", + "description": "The LineRenderingConfig gives all details onto how to render a single line of a feature.\n\nThis can be used if:\n\n- The feature is a line\n- The feature is an area" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The color for way-elements and SVG-elements.\nIf the value starts with \"--\", the style of the body element will be queried for the corresponding variable instead" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": [ + "string", + "number" + ] + } + ], + "description": "The stroke-width for way-elements" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A dasharray, e.g. \"5 6\"\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap',\nDefault value: \"\" (empty string == full line)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The form at the end of a line" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "no", + "yes" + ], + "type": "string" + } + ], + "description": "Whether or not to fill polygons" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The color to fill a polygon with.\nIf undefined, this will be slightly more opaque version of the stroke line" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "number" + } + ], + "description": "The number of pixels this line should be moved.\nUse a positive numbe to move to the right, a negative to move to the left (left/right as defined by the drawing direction of the line).\n\nIMPORTANT: MapComplete will already normalize 'key:both:property' and 'key:both' into the corresponding 'key:left' and 'key:right' tagging (same for 'sidewalk=left/right/both' which is rewritten to 'sidewalk:left' and 'sidewalk:right')\nThis simplifies programming. Refer to the CalculatedTags.md-documentation for more details" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings" + ], + "type": "object", + "description": "The LineRenderingConfig gives all details onto how to render a single line of a feature.\n\nThis can be used if:\n\n- The feature is a line\n- The feature is an area" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The color for way-elements and SVG-elements.\nIf the value starts with \"--\", the style of the body element will be queried for the corresponding variable instead" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "color", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": [ + "string", + "number" + ] + } + ], + "description": "The stroke-width for way-elements" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "width", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A dasharray, e.g. \"5 6\"\nThe dasharray defines 'pixels of line, pixels of gap, pixels of line, pixels of gap',\nDefault value: \"\" (empty string == full line)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "dashArray", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The form at the end of a line" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "lineCap", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "no", + "yes" + ], + "type": "string" + } + ], + "description": "Whether or not to fill polygons" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fill", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The color to fill a polygon with.\nIf undefined, this will be slightly more opaque version of the stroke line" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "fillColor", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "number" + } + ], + "description": "The number of pixels this line should be moved.\nUse a positive numbe to move to the right, a negative to move to the left (left/right as defined by the drawing direction of the line).\n\nIMPORTANT: MapComplete will already normalize 'key:both:property' and 'key:both' into the corresponding 'key:left' and 'key:right' tagging (same for 'sidewalk=left/right/both' which is rewritten to 'sidewalk:left' and 'sidewalk:right')\nThis simplifies programming. Refer to the CalculatedTags.md-documentation for more details" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "offset", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings" + ], + "type": "object", + "description": "The PointRenderingConfig gives all details onto how to render a single point of a feature.\n\nThis can be used if:\n\n- The feature is a point\n- To render something at the centroid of an area, or at the start, end or projected centroid of a way" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "location" + ], + "type": "array", + "description": "All the locations that this point should be rendered at.\nPossible values are:\n- `point`: only renders points at their location\n- `centroid`: show a symbol at the centerpoint of a (multi)Linestring and (multi)polygon. Points will _not_ be rendered with this\n- `projected_centerpoint`: Only on (multi)linestrings: calculate the centerpoint and snap it to the way\n- `start` and `end`: only on linestrings: add a point to the first/last coordinate of the LineString" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The icon for an element.\nNote that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets.\n\nThe result of the icon is rendered as follows:\nthe resulting string is interpreted as a _list_ of items, separated by \";\". The bottommost layer is the first layer.\nAs a result, on could use a generic pin, then overlay it with a specific icon.\nTo make things even more practical, one can use all SVG's from the folder \"assets/svg\" and _substitute the color_ in it.\nE.g. to draw a red pin, use \"pin:#f00\", to have a green circle with your icon on top, use `circle:#0f0;`\n\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "icon", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges" + ], + "type": "array", + "description": "A list of extra badges to show next to the icon as small badge\nThey will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout.\n\nNote: strings are interpreted as icons, so layering and substituting is supported. You can use `circle:white;./my_icon.svg` to add a background circle" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then" + ], + "typeHint": "icon", + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Badge to show\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconBadges", + "then", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A string containing \"width,height\" or \"width,height,anchorpoint\" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ...\nDefault is '40,40,center'" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "iconSize", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "The rotation of an icon, useful for e.g. directions.\nUsage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)``" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotation", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A HTML-fragment that is shown below the icon, for example:\n
{name}
\n\nIf the icon is undefined, then the label is shown in the center of the feature.\nNote that, if the wayhandling hides the icon then no label is shown as well." + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "label", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A snippet of css code which is applied onto the container of the entire marker" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "css", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A snippet of css-classes which are applied onto the container of the entire marker. They can be space-separated" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Css that is applied onto the label" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCss", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "Css classes that are applied onto the label; can be space-separated" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "labelCssClasses", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ], + "description": "If the map is pitched, the marker will stay parallel to the screen.\nSet to 'map' if you want to put it flattened on the map" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "pitchAlignment", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "enum": [ + "canvas", + "map" + ], + "type": "string" + } + ], + "description": "If the map is rotated, the icon will still point to the north if no rotation was applied" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "then" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "mapRendering", + "renderings", + "rotationAlignment", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "override", + "passAllFeatures" + ], + "type": "boolean", + "description": "If set, this layer will pass all the features it receives onto the next layer.\nThis is ideal for decoration, e.g. directionss on cameras" + }, + { + "path": [ + "layers", + "override", + "presets" + ], + "type": "array", + "description": "Presets for this layer.\nA preset shows up when clicking the map on a without data (or when right-clicking/long-pressing);\nit will prompt the user to add a new point.\n\nThe most important aspect are the tags, which define which tags the new point will have;\nThe title is shown in the dialog, along with the first sentence of the description.\n\nUpon confirmation, the full description is shown beneath the buttons - perfect to add pictures and examples.\n\nNote: the icon of the preset is determined automatically based on the tags and the icon above. Don't worry about that!\nNB: if no presets are defined, the popup to add new points doesn't show up at all" + }, + { + "path": [ + "layers", + "override", + "presets", + "title" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "The title - shown on the 'add-new'-button.\n\nThis should include the article of the noun, e.g. 'a hydrant', 'a bicycle pump'.\nThis text will be inserted into `Add {category} here`, becoming `Add a hydrant here`.\n\nDo _not_ indicate 'new': 'add a new shop here' is incorrect, as the shop might have existed forever, it could just be unmapped!" + }, + { + "path": [ + "layers", + "override", + "presets", + "tags" + ], + "type": "array", + "description": "The tags to add. It determines the icon too" + }, + { + "path": [ + "layers", + "override", + "presets", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "The _first sentence_ of the description is shown on the button of the `add` menu.\nThe full description is shown in the confirmation dialog.\n\n(The first sentence is until the first '.'-character in the description)" + }, + { + "path": [ + "layers", + "override", + "presets", + "exampleImages" + ], + "typeHint": "image", + "type": "array", + "description": "Example images, which show real-life pictures of what such a feature might look like\n\nType: image" + }, + { + "path": [ + "layers", + "override", + "presets", + "preciseInput" + ], + "type": [ + { + "type": "object", + "properties": { + "preferredBackground": { + "description": "The type of background picture", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "snapToLayer": { + "description": "If specified, these layers will be shown to and the new point will be snapped towards it", + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "maxSnapDistance": { + "description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10", "type": "number" } } }, { "enum": [ - false + true ], "type": "boolean" } ], - "description": "If defined, data will be clustered.\nDefaults to {maxZoom: 16, minNeeded: 500}" + "description": "If set, the user will prompted to confirm the location before actually adding the data.\nThis will be with a 'drag crosshair'-method.\n\nIf 'preferredBackgroundCategory' is set, the element will attempt to pick a background layer of that category." }, { "path": [ - "clustering", - "maxZoom" + "layers", + "override", + "presets", + "preciseInput", + "preferredBackground" ], - "type": "number", - "description": "All zoom levels above 'maxzoom' are not clustered anymore.\nDefaults to 18" + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "The type of background picture" }, { "path": [ - "clustering", - "minNeededElements" + "layers", + "override", + "presets", + "preciseInput", + "snapToLayer" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "If specified, these layers will be shown to and the new point will be snapped towards it" + }, + { + "path": [ + "layers", + "override", + "presets", + "preciseInput", + "maxSnapDistance" ], "type": "number", - "description": "The number of elements per tile needed to start clustering\nIf clustering is defined, defaults to 250" + "description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10" + }, + { + "path": [ + "layers", + "override", + "tagRenderings" + ], + "type": [ + { + "$ref": "#/definitions/QuestionableTagRenderingConfigJson" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "builtin": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "override": { + "$ref": "#/definitions/Partial" + } + }, + "required": [ + "builtin", + "override" + ] + }, + { + "allOf": [ + { + "$ref": "#/definitions/default<(string|QuestionableTagRenderingConfigJson|{builtin:string;override:Partial;})[]>" + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + ] + }, + { + "type": "string" + } + ], + "description": "All the tag renderings.\nA tag rendering is a block that either shows the known value or asks a question.\n\nRefer to the class `TagRenderingConfigJson` to see the possibilities.\n\nNote that we can also use a string here - where the string refers to a tag rendering defined in `assets/questions/questions.json`,\nwhere a few very general questions are defined e.g. website, phone number, ...\nFurthermore, _all_ the questions of another layer can be reused with `otherlayer.*`\nIf you need only a single of the tagRenderings, use `otherlayer.tagrenderingId`\nIf one or more questions have a 'group' or 'label' set, select all the entries with the corresponding group or label with `otherlayer.*group`\nRemark: if a tagRendering is 'lent' from another layer, the 'source'-tags are copied and added as condition.\nIf they are not wanted, remove them with an override\n\nA special value is 'questions', which indicates the location of the questions box. If not specified, it'll be appended to the bottom of the featureInfobox.\n\nAt last, one can define a group of renderings where parts of all strings will be replaced by multiple other strings.\nThis is mainly create questions for a 'left' and a 'right' side of the road.\nThese will be grouped and questions will be asked together" + }, + { + "path": [ + "layers", + "override", + "tagRenderings" + ], + "type": "object", + "description": "A QuestionableTagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nIf the desired tags are missing and a question is defined, a question will be shown instead." + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "question" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "questionHint" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A hint which is shown in subtle text under the question.\nThis can give some extra information on what the answer should ook like" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "freeform", + "type" + ], + "type": "string", + "description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "freeform", + "placeholder" + ], + "description": "A (translated) text that is shown (as gray text) within the textfield" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "freeform", + "helperArgs" + ], + "type": "array", + "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "freeform", + "addExtraTags" + ], + "type": "array", + "description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "freeform", + "inline" + ], + "type": "boolean", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present." + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "freeform", + "default" + ], + "type": "string", + "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "multiAnswer" + ], + "type": "boolean", + "description": "If true, use checkboxes instead of radio buttons when asking the question" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "then" + ], + "typeHint": "rendered", + "description": "Shown if the 'if is fulfilled\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "Size of the image", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An extra icon supporting the choice\nType: icon" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "Size of the image" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "hideInAnswer" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": [ + "string", + "boolean" + ] + } + ], + "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n\n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "hideInAnswer" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "hideInAnswer", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "hideInAnswer", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "hideInAnswer" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "hideInAnswer", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "hideInAnswer", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "hideInAnswer", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "ifnot" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "ifnot" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "ifnot", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "ifnot", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "ifnot" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "ifnot", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "ifnot", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "ifnot", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "addExtraTags" + ], + "type": "array", + "description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer.\n\nThis can be used e.g. to erase other keys which indicate the 'not' value:\n```json\n{\n \"if\": \"crossing:marking=rainbow\",\n \"then\": \"This is a rainbow crossing\",\n \"addExtraTags\": \"not:crossing:marking=\"\n}\n```" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "priorityIf" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If the searchable selector is picked, mappings with this item will have priority and show up even if the others are hidden\nUse this sparingly" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "priorityIf" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "priorityIf", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "priorityIf", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "priorityIf" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "priorityIf", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "priorityIf", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "priorityIf", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "mappings", + "#" + ], + "type": "string", + "description": "Used for comments or to disable a validation\n\nignore-image-in-then: normally, a `then`-clause is not allowed to have an `img`-html-element as icons are preferred. In some cases (most notably title-icons), this is allowed" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "question" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "questionHint" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A hint which is shown in subtle text under the question.\nThis can give some extra information on what the answer should ook like" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "freeform", + "type" + ], + "type": "string", + "description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "freeform", + "placeholder" + ], + "description": "A (translated) text that is shown (as gray text) within the textfield" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "freeform", + "helperArgs" + ], + "type": "array", + "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "freeform", + "addExtraTags" + ], + "type": "array", + "description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "freeform", + "inline" + ], + "type": "boolean", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present." + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "freeform", + "default" + ], + "type": "string", + "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "multiAnswer" + ], + "type": "boolean", + "description": "If true, use checkboxes instead of radio buttons when asking the question" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "then" + ], + "typeHint": "rendered", + "description": "Shown if the 'if is fulfilled\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "Size of the image", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An extra icon supporting the choice\nType: icon" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "Size of the image" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "hideInAnswer" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": [ + "string", + "boolean" + ] + } + ], + "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n\n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "hideInAnswer" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "hideInAnswer", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "hideInAnswer", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "hideInAnswer" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "hideInAnswer", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "hideInAnswer", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "hideInAnswer", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "ifnot" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "ifnot" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "ifnot", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "ifnot", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "ifnot" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "ifnot", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "ifnot", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "ifnot", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "addExtraTags" + ], + "type": "array", + "description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer.\n\nThis can be used e.g. to erase other keys which indicate the 'not' value:\n```json\n{\n \"if\": \"crossing:marking=rainbow\",\n \"then\": \"This is a rainbow crossing\",\n \"addExtraTags\": \"not:crossing:marking=\"\n}\n```" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "priorityIf" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If the searchable selector is picked, mappings with this item will have priority and show up even if the others are hidden\nUse this sparingly" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "priorityIf" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "priorityIf", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "priorityIf", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "priorityIf" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "priorityIf", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "priorityIf", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "priorityIf", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "mappings", + "#" + ], + "type": "string", + "description": "Used for comments or to disable a validation\n\nignore-image-in-then: normally, a `then`-clause is not allowed to have an `img`-html-element as icons are preferred. In some cases (most notably title-icons), this is allowed" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "override", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings" + ], + "type": "object", + "description": "Rewrites and multiplies the given renderings of type T.\n\nThis can be used for introducing many similar questions automatically,\nwhich also makes translations easier.\n\n(Note that the key does _not_ need to be wrapped in {}.\nHowever, we recommend to use them if the key is used in a translation, as missing keys will be picked up and warned for by the translation scripts)\n\nFor example:\n\n```\n{\n rewrite: {\n sourceString: [\"key\", \"a|b|c\"],\n into: [\n [\"X\", 0]\n [\"Y\", 1],\n [\"Z\", 2]\n ],\n renderings: [{\n \"key\":\"a|b|c\"\n }]\n }\n}\n```\nwill result in _three_ copies (as the values to rewrite into have three values, namely:\n\n[\n {\n # The first pair: key --> X, a|b|c --> 0\n \"X\": 0\n },\n {\n \"Y\": 1\n },\n {\n \"Z\": 2\n }\n\n]" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings" + ], + "type": "object", + "description": "A QuestionableTagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nIf the desired tags are missing and a question is defined, a question will be shown instead." + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "question" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "questionHint" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A hint which is shown in subtle text under the question.\nThis can give some extra information on what the answer should ook like" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "freeform", + "type" + ], + "type": "string", + "description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "freeform", + "placeholder" + ], + "description": "A (translated) text that is shown (as gray text) within the textfield" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "freeform", + "helperArgs" + ], + "type": "array", + "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "freeform", + "addExtraTags" + ], + "type": "array", + "description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "freeform", + "inline" + ], + "type": "boolean", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present." + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "freeform", + "default" + ], + "type": "string", + "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "multiAnswer" + ], + "type": "boolean", + "description": "If true, use checkboxes instead of radio buttons when asking the question" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "then" + ], + "typeHint": "rendered", + "description": "Shown if the 'if is fulfilled\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "Size of the image", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An extra icon supporting the choice\nType: icon" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "Size of the image" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "hideInAnswer" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": [ + "string", + "boolean" + ] + } + ], + "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n\n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "hideInAnswer" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "hideInAnswer", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "hideInAnswer", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "hideInAnswer" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "hideInAnswer", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "hideInAnswer", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "hideInAnswer", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "ifnot" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "ifnot" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "ifnot", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "ifnot", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "ifnot" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "ifnot", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "ifnot", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "ifnot", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "addExtraTags" + ], + "type": "array", + "description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer.\n\nThis can be used e.g. to erase other keys which indicate the 'not' value:\n```json\n{\n \"if\": \"crossing:marking=rainbow\",\n \"then\": \"This is a rainbow crossing\",\n \"addExtraTags\": \"not:crossing:marking=\"\n}\n```" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "priorityIf" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If the searchable selector is picked, mappings with this item will have priority and show up even if the others are hidden\nUse this sparingly" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "priorityIf" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "priorityIf", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "priorityIf", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "priorityIf" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "priorityIf", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "priorityIf", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "priorityIf", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "mappings", + "#" + ], + "type": "string", + "description": "Used for comments or to disable a validation\n\nignore-image-in-then: normally, a `then`-clause is not allowed to have an `img`-html-element as icons are preferred. In some cases (most notably title-icons), this is allowed" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "question" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "If it turns out that this tagRendering doesn't match _any_ value, then we show this question.\nIf undefined, the question is never asked and this tagrendering is read-only" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "questionHint" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A hint which is shown in subtle text under the question.\nThis can give some extra information on what the answer should ook like" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "freeform", + "type" + ], + "type": "string", + "description": "The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "freeform", + "placeholder" + ], + "description": "A (translated) text that is shown (as gray text) within the textfield" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "freeform", + "helperArgs" + ], + "type": "array", + "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "freeform", + "addExtraTags" + ], + "type": "array", + "description": "If a value is added with the textfield, these extra tag is addded.\nUseful to add a 'fixme=freeform textfield used - to be checked'" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "freeform", + "inline" + ], + "type": "boolean", + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present." + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "freeform", + "default" + ], + "type": "string", + "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "multiAnswer" + ], + "type": "boolean", + "description": "If true, use checkboxes instead of radio buttons when asking the question" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "then" + ], + "typeHint": "rendered", + "description": "Shown if the 'if is fulfilled\nType: rendered" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "Size of the image", + "type": "string" + } + }, + "required": [ + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An extra icon supporting the choice\nType: icon" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "Size of the image" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "hideInAnswer" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": [ + "string", + "boolean" + ] + } + ], + "description": "In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).\n\nIn the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.\nIn this case, one of the mappings can be hiden by setting this flag.\n\nTo demonstrate an example making a default assumption:\n\nmappings: [\n {\n if: \"access=\", -- no access tag present, we assume accessible\n then: \"Accessible to the general public\",\n hideInAnswer: true\n },\n {\n if: \"access=yes\",\n then: \"Accessible to the general public\", -- the user selected this, we add that to OSM\n },\n {\n if: \"access=no\",\n then: \"Not accessible to the public\"\n }\n]\n\n\nFor example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.\nThen, we would add two mappings:\n{\n if: \"operator=Agentschap Natuur en Bos\" -- the non-abbreviated version which should be uploaded\n then: \"Maintained by Agentschap Natuur en Bos\"\n},\n{\n if: \"operator=ANB\", -- we don't want to upload abbreviations\n then: \"Maintained by Agentschap Natuur en Bos\"\n hideInAnswer: true\n}\n\nHide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.\nKeep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch\n\ne.g., for toilets: if \"wheelchair=no\", we know there is no wheelchair dedicated room.\nFor the location of the changing table, the option \"in the wheelchair accessible toilet is weird\", so we write:\n\n{\n \"question\": \"Where is the changing table located?\"\n \"mappings\": [\n {\"if\":\"changing_table:location=female\",\"then\":\"In the female restroom\"},\n {\"if\":\"changing_table:location=male\",\"then\":\"In the male restroom\"},\n {\"if\":\"changing_table:location=wheelchair\",\"then\":\"In the wheelchair accessible restroom\", \"hideInAnswer\": \"wheelchair=no\"},\n\n ]\n}\n\nAlso have a look for the meta-tags\n{\n if: \"operator=Agentschap Natuur en Bos\",\n then: \"Maintained by Agentschap Natuur en Bos\",\n hideInAnswer: \"_country!=be\"\n}" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "hideInAnswer" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "hideInAnswer", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "hideInAnswer", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "hideInAnswer" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "hideInAnswer", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "hideInAnswer", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "hideInAnswer", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "ifnot" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only applicable if 'multiAnswer' is set.\nThis is for situations such as:\n`accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.\nThis can be done with `ifnot`\nNote that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.\nIf this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "ifnot" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "ifnot", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "ifnot", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "ifnot" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "ifnot", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "ifnot", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "ifnot", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "addExtraTags" + ], + "type": "array", + "description": "If chosen as answer, these tags will be applied as well onto the object.\nNot compatible with multiAnswer.\n\nThis can be used e.g. to erase other keys which indicate the 'not' value:\n```json\n{\n \"if\": \"crossing:marking=rainbow\",\n \"then\": \"This is a rainbow crossing\",\n \"addExtraTags\": \"not:crossing:marking=\"\n}\n```" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "priorityIf" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If the searchable selector is picked, mappings with this item will have priority and show up even if the others are hidden\nUse this sparingly" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "priorityIf" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "priorityIf", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "priorityIf", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "priorityIf" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "priorityIf", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "priorityIf", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "priorityIf", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "mappings", + "#" + ], + "type": "string", + "description": "Used for comments or to disable a validation\n\nignore-image-in-then: normally, a `then`-clause is not allowed to have an `img`-html-element as icons are preferred. In some cases (most notably title-icons), this is allowed" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "description" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "render" + ], + "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "tagRenderings", + "renderings", + "override", + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "filter" + ], + "type": [ + { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/default_1" + }, + { + "type": "string" + } + ] + } + }, + { + "type": "object", + "properties": { + "sameAs": { + "type": "string" + } + }, + "required": [ + "sameAs" + ] + } + ], + "description": "All the extra questions for filtering.\nIf a string is given, mapComplete will search in 'filters.json' for the appropriate filter or will try to parse it as `layername.filterid` and us that one" + }, + { + "path": [ + "layers", + "override", + "filter", + "id" + ], + "type": "string", + "description": "An id/name for this filter, used to set the URL parameters" + }, + { + "path": [ + "layers", + "override", + "filter", + "options" + ], + "type": "array", + "description": "The options for a filter\nIf there are multiple options these will be a list of radio buttons\nIf there is only one option this will be a checkbox\nFiltering is done based on the given osmTags that are compared to the objects in that layer.\n\nAn example which searches by name:\n\n```\n{\n \"id\": \"shop-name\",\n \"options\": [\n {\n \"fields\": [\n {\n \"name\": \"search\",\n \"type\": \"string\"\n }\n ],\n \"osmTags\": \"name~i~.*{search}.*\",\n \"question\": {\n \"en\": \"Only show shops with name {search}\",\n }\n }\n ]\n }\n ```" + }, + { + "path": [ + "layers", + "override", + "filter", + "options", + "osmTags" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "filter", + "options", + "osmTags" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "filter", + "options", + "osmTags", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "filter", + "options", + "osmTags", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "filter", + "options", + "osmTags" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "filter", + "options", + "osmTags", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "filter", + "options", + "osmTags", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "filter", + "options", + "osmTags", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "filter", + "options", + "fields", + "name" + ], + "type": "string", + "description": "If name is `search`, use \"_first_comment~.*{search}.*\" as osmTags" + }, + { + "path": [ + "layers", + "override", + "filter", + "#" + ], + "type": "string", + "description": "Used for comments or to disable a check\n\n\"ignore-possible-duplicate\": disables a check in `DetectDuplicateFilters` which complains that a filter can be replaced by a filter from the `filters`-library-layer" + }, + { + "path": [ + "layers", + "override", + "deletion" + ], + "type": [ + { + "$ref": "#/definitions/DeleteConfigJson" + }, + { + "type": "boolean" + } + ], + "description": "This block defines under what circumstances the delete dialog is shown for objects of this layer.\nIf set, a dialog is shown to the user to (soft) delete the point.\nThe dialog is built to be user friendly and to prevent mistakes.\nIf deletion is not possible, the dialog will hide itself and show the reason of non-deletability instead.\n\nTo configure, the following values are possible:\n\n- false: never ever show the delete button\n- true: show the default delete button\n- undefined: use the mapcomplete default to show deletion or not. Currently, this is the same as 'false' but this will change in the future\n- or: a hash with options (see below)\n\n The delete dialog\n =================\n\n\n\n#### Hard deletion if enough experience\n\nA feature can only be deleted from OpenStreetMap by mapcomplete if:\n\n- It is a node\n- No ways or relations use the node\n- The logged-in user has enough experience OR the user is the only one to have edited the point previously\n- The logged-in user has no unread messages (or has a ton of experience)\n- The user did not select one of the 'non-delete-options' (see below)\n\nIn all other cases, a 'soft deletion' is used.\n\n#### Soft deletion\n\nA 'soft deletion' is when the point isn't deleted from OSM but retagged so that it'll won't how up in the mapcomplete theme anymore.\nThis makes it look like it was deleted, without doing damage. A fixme will be added to the point.\n\nNote that a soft deletion is _only_ possible if these tags are provided by the theme creator, as they'll be different for every theme\n\n#### No-delete options\n\nIn some cases, the contributor might want to delete something for the wrong reason (e.g. someone who wants to have a path removed \"because the path is on their private property\").\nHowever, the path exists in reality and should thus be on OSM - otherwise the next contributor will pass by and notice \"hey, there is a path missing here! Let me redraw it in OSM!)\n\nThe correct approach is to retag the feature in such a way that it is semantically correct *and* that it doesn't show up on the theme anymore.\nA no-delete option is offered as 'reason to delete it', but secretly retags." + }, + { + "path": [ + "layers", + "override", + "deletion", + "extraDeleteReasons" + ], + "type": "array", + "description": "*\nBy default, three reasons to delete a point are shown:\n\n- The point does not exist anymore\n- The point was a testing point\n- THe point could not be found\n\nHowever, for some layers, there might be different or more specific reasons for deletion which can be user friendly to set, e.g.:\n\n- the shop has closed\n- the climbing route has been closed of for nature conservation reasons\n- ...\n\nThese reasons can be stated here and will be shown in the list of options the user can choose from" + }, + { + "path": [ + "layers", + "override", + "deletion", + "extraDeleteReasons", + "explanation" + ], + "description": "The text that will be shown to the user - translatable" + }, + { + "path": [ + "layers", + "override", + "deletion", + "extraDeleteReasons", + "changesetMessage" + ], + "type": "string", + "description": "The text that will be uploaded into the changeset or will be used in the fixme in case of a soft deletion\nShould be a few words, in english" + }, + { + "path": [ + "layers", + "override", + "deletion", + "nonDeleteMappings" + ], + "type": "array", + "description": "In some cases, a (starting) contributor might wish to delete a feature even though deletion is not appropriate.\n(The most relevant case are small paths running over private property. These should be marked as 'private' instead of deleted, as the community might trace the path again from aerial imagery, gettting us back to the original situation).\n\nBy adding a 'nonDeleteMapping', an option can be added into the list which will retag the feature.\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!" + }, + { + "path": [ + "layers", + "override", + "deletion", + "nonDeleteMappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "deletion", + "nonDeleteMappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "deletion", + "nonDeleteMappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "deletion", + "nonDeleteMappings", + "then" + ], + "description": "The human explanation for the options" + }, + { + "path": [ + "layers", + "override", + "deletion", + "softDeletionTags" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "In some cases, the contributor is not allowed to delete the current feature (e.g. because it isn't a point, the point is referenced by a relation or the user isn't experienced enough).\nTo still offer the user a 'delete'-option, the feature is retagged with these tags. This is a soft deletion, as the point isn't actually removed from OSM but rather marked as 'disused'\nIt is important that the feature will be retagged in such a way that it won't be picked up by the layer anymore!\n\nExample (note that \"amenity=\" erases the 'amenity'-key alltogether):\n```\n{\n \"and\": [\"disussed:amenity=public_bookcase\", \"amenity=\"]\n}\n```\n\nor (notice the use of the ':='-tag to copy the old value of 'shop=*' into 'disused:shop='):\n```\n{\n \"and\": [\"disused:shop:={shop}\", \"shop=\"]\n}\n```" + }, + { + "path": [ + "layers", + "override", + "deletion", + "softDeletionTags" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "deletion", + "softDeletionTags", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "deletion", + "softDeletionTags", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "deletion", + "softDeletionTags" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "deletion", + "softDeletionTags", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "override", + "deletion", + "softDeletionTags", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "deletion", + "softDeletionTags", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "override", + "deletion", + "neededChangesets" + ], + "type": "number", + "description": "*\nBy default, the contributor needs 20 previous changesets to delete points edited by others.\nFor some small features (e.g. bicycle racks) this is too much and this requirement can be lowered or dropped, which can be done here." + }, + { + "path": [ + "layers", + "override", + "deletion", + "omitDefaultDeleteReasons" + ], + "type": "boolean", + "description": "Set this flag if the default delete reasons should be omitted from the dialog.\nThis requires at least one extraDeleteReason or nonDeleteMapping" + }, + { + "path": [ + "layers", + "override", + "allowMove" + ], + "type": [ + { + "$ref": "#/definitions/default_3" + }, + { + "type": "boolean" + } + ], + "description": "Indicates if a point can be moved and configures the modalities.\n\nA feature can be moved by MapComplete if:\n\n- It is a point\n- The point is _not_ part of a way or a a relation.\n\nOff by default. Can be enabled by setting this flag or by configuring." + }, + { + "path": [ + "layers", + "override", + "allowMove", + "enableImproveAccuracy" + ], + "type": "boolean", + "description": "One default reason to move a point is to improve accuracy.\nSet to false to disable this reason" + }, + { + "path": [ + "layers", + "override", + "allowMove", + "enableRelocation" + ], + "type": "boolean", + "description": "One default reason to move a point is because it has relocated\nSet to false to disable this reason" + }, + { + "path": [ + "layers", + "override", + "allowSplit" + ], + "type": "boolean", + "description": "If set, a 'split this way' button is shown on objects rendered as LineStrings, e.g. highways.\n\nIf the way is part of a relation, MapComplete will attempt to update this relation as well" + }, + { + "path": [ + "layers", + "override", + "units" + ], + "type": "object", + "description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\nNot only do we want to write consistent data to OSM, we also want to present this consistently to the user.\nThis is handled by defining units.\n\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\n human: {\n en: \"kilometer/hour\",\n nl: \"kilometer/uur\"\n },\n humanShort: {\n en: \"km/h\",\n nl: \"km/u\"\n }\n},\n{\n canoncialDenomination: \"mph\",\n ... similar for miles an hour ...\n}\n]\n```\n\n\nIf this is defined, then every key which the denominations apply to (`maxspeed`, `maxspeed:hgv` and `maxspeed:bus`) will be rewritten at the metatagging stage:\nevery value will be parsed and the canonical extension will be added add presented to the other parts of the code.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given" + }, + { + "path": [ + "layers", + "override", + "units", + "appliesToKey" + ], + "type": "array", + "description": "Every key from this list will be normalized.\n\nTo render a united value properly, use" + }, + { + "path": [ + "layers", + "override", + "units", + "eraseInvalidValues" + ], + "type": "boolean", + "description": "If set, invalid values will be erased in the MC application (but not in OSM of course!)\nBe careful with setting this" + }, + { + "path": [ + "layers", + "override", + "units", + "applicableUnits" + ], + "type": "array", + "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"" + }, + { + "path": [ + "layers", + "override", + "units", + "applicableUnits", + "useIfNoUnitGiven" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "boolean" + } + ], + "description": "If this evaluates to true and the value to interpret has _no_ unit given, assumes that this unit is meant.\nAlternatively, a list of country codes can be given where this acts as the default interpretation\n\nE.g., a denomination using \"meter\" would probably set this flag to \"true\";\na denomination for \"mp/h\" will use the condition \"_country=gb\" to indicate that it is the default in the UK.\n\nIf none of the units indicate that they are the default, the first denomination will be used instead" + }, + { + "path": [ + "layers", + "override", + "units", + "applicableUnits", + "canonicalDenomination" + ], + "type": "string", + "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used" + }, + { + "path": [ + "layers", + "override", + "units", + "applicableUnits", + "canonicalDenominationSingular" + ], + "type": "string", + "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here" + }, + { + "path": [ + "layers", + "override", + "units", + "applicableUnits", + "alternativeDenomination" + ], + "type": "array", + "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well" + }, + { + "path": [ + "layers", + "override", + "units", + "applicableUnits", + "human" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" + }, + { + "path": [ + "layers", + "override", + "units", + "applicableUnits", + "humanSingular" + ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}" + }, + { + "path": [ + "layers", + "override", + "units", + "applicableUnits", + "prefix" + ], + "type": "boolean", + "description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field" + }, + { + "path": [ + "layers", + "override", + "units", + "defaultInput" + ], + "type": "string", + "description": "In some cases, the default denomination is not the most user friendly to input.\nE.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.\n\nWhen a default input method should be used, this can be specified by setting the canonical denomination here, e.g.\n`defaultInput: \"cm\"`. This must be a denomination which appears in the applicableUnits" + }, + { + "path": [ + "layers", + "override", + "syncSelection" + ], + "type": "string", + "description": "If set, synchronizes whether or not this layer is enabled.\n\nno: Do not sync at all, always revert to default\nlocal: keep selection on local storage\ntheme-only: sync via OSM, but this layer will only be toggled in this theme\nglobal: all layers with this ID will be synced accross all themes" + }, + { + "path": [ + "layers", + "override", + "#" + ], + "type": "string", + "description": "Used for comments and/or to disable some checks\n\nno-question-hint-check: disables a check in MiscTagRenderingChecks which complains about 'div', 'span' or 'class=subtle'-HTML elements in the tagRendering" + }, + { + "path": [ + "layers", + "hideTagRenderingsWithLabels" + ], + "type": "array", + "description": "TagRenderings with any of these labels will be removed from the layer.\nNote that the 'id' and 'group' are considered labels too" }, { "path": [ @@ -23710,5 +81108,12 @@ ], "type": "number", "description": "Set a different timeout for overpass queries - in seconds. Default: 30s" + }, + { + "path": [ + "enableNodeDatabase" + ], + "type": "boolean", + "description": "Enables tracking of all nodes when data is loaded.\nThis is useful for the 'ImportWay' and 'ConflateWay'-buttons who need this database.\n\nNote: this flag will be automatically set." } ] \ No newline at end of file diff --git a/assets/questionabletagrenderingconfigmeta.json b/assets/questionabletagrenderingconfigmeta.json index ea857adf75..0d6d0344a9 100644 --- a/assets/questionabletagrenderingconfigmeta.json +++ b/assets/questionabletagrenderingconfigmeta.json @@ -76,7 +76,7 @@ "inline" ], "type": "boolean", - "description": "When set, influences the way a question is asked.\nInstead of showing a full-widht text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout." + "description": "When set, influences the way a question is asked.\nInstead of showing a full-width text field, the text field will be shown within the rendering of the question.\n\nThis combines badly with special input elements, as it'll distort the layout.\nNote that this will be set automatically if no special elements are present." }, { "path": [ @@ -603,13 +603,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "labels" @@ -617,10 +610,35 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -628,6 +646,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -746,5 +798,122 @@ ], "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" } ] \ No newline at end of file diff --git a/assets/tagrenderingconfigmeta.json b/assets/tagrenderingconfigmeta.json index 2c5662c6fe..4ff420c81d 100644 --- a/assets/tagrenderingconfigmeta.json +++ b/assets/tagrenderingconfigmeta.json @@ -11,13 +11,6 @@ "type": "string", "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" }, - { - "path": [ - "group" - ], - "type": "string", - "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." - }, { "path": [ "labels" @@ -25,10 +18,35 @@ "type": "array", "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" }, + { + "path": [ + "classes" + ], + "type": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ], + "description": "A list of css-classes to apply to the entire tagRendering if the answer is known (not applied on the question).\nThis is only for advanced users" + }, { "path": [ "description" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A human-readable text explaining what this tagRendering does" }, { @@ -36,6 +54,40 @@ "render" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "object", + "properties": { + "special": { + "allOf": [ + { + "$ref": "#/definitions/Record>" + }, + { + "type": "object", + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ] + } + ] + } + }, + "required": [ + "special" + ] + }, + { + "type": "string" + } + ], "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" }, { @@ -155,6 +207,123 @@ "type": "object", "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" }, + { + "path": [ + "metacondition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "If set, this tag will be evaluated agains the _usersettings/application state_ table.\nEnable 'show debug info' in user settings to see available options.\nNote that values with an underscore depicts _application state_ (including metainfo about the user) whereas values without an underscore depict _user settings_" + }, + { + "path": [ + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "metacondition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "metacondition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "metacondition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "metacondition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "metacondition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, { "path": [ "freeform" @@ -229,6 +398,14 @@ "then" ], "typeHint": "rendered", + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" }, { From 3c0a19ebc6c9c90ab7d20441ff98906ed2e98701 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 21 Apr 2023 16:20:55 +0200 Subject: [PATCH 057/257] Chore: fix import --- UI/BigComponents/OverlayToggle.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/BigComponents/OverlayToggle.svelte b/UI/BigComponents/OverlayToggle.svelte index 3b350b285c..6d5961f25d 100644 --- a/UI/BigComponents/OverlayToggle.svelte +++ b/UI/BigComponents/OverlayToggle.svelte @@ -7,7 +7,7 @@ import { UIEventSource } from "../../Logic/UIEventSource"; import Tr from "../Base/Tr.svelte"; import Translations from "../i18n/Translations"; import { Translation } from "../i18n/Translation"; -import { RasterLayerProperties } from "../../Models/RasterLayerProperties"; +import type { RasterLayerProperties } from "../../Models/RasterLayerProperties"; export let layerproperties : RasterLayerProperties export let state: {isDisplayed: UIEventSource}; From ee4b0fd238d6373f3d09dab4b326c02fcc2ac6ea Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 21 Apr 2023 16:47:25 +0200 Subject: [PATCH 058/257] Refactoring: add hotkeys to switch backgrounds --- Models/RasterLayerProperties.ts | 20 ++++++------- Models/ThemeViewState.ts | 52 +++++++++++++++++++++++++++++++-- langs/ca.json | 1 - langs/cs.json | 1 - langs/de.json | 1 - langs/en.json | 6 ++-- langs/fr.json | 1 - langs/nl.json | 1 - 8 files changed, 63 insertions(+), 20 deletions(-) diff --git a/Models/RasterLayerProperties.ts b/Models/RasterLayerProperties.ts index a4bf0e09bc..2686ebe2af 100644 --- a/Models/RasterLayerProperties.ts +++ b/Models/RasterLayerProperties.ts @@ -1,3 +1,12 @@ +export type EliCategory = + | "photo" + | "map" + | "historicmap" + | "osmbasedmap" + | "historicphoto" + | "qa" + | "elevation" + | "other" export interface RasterLayerProperties { /** * The name of the imagery source @@ -9,16 +18,7 @@ export interface RasterLayerProperties { readonly id: string readonly url: string - readonly category?: - | string - | "photo" - | "map" - | "historicmap" - | "osmbasedmap" - | "historicphoto" - | "qa" - | "elevation" - | "other" + readonly category?: string | EliCategory readonly attribution?: { readonly url?: string diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index b36be04b00..53afde28d9 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -45,6 +45,7 @@ import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sour import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer" import { Utils } from "../Utils" +import { EliCategory } from "./RasterLayerProperties" /** * @@ -291,14 +292,59 @@ export default class ThemeViewState implements SpecialVisualizationState { } } ) - /* + Hotkeys.RegisterHotkey( { shift: "O" }, Translations.t.hotkeyDocumentation.selectMapnik, () => { - this.state.backgroundLayer.setData(AvailableBaseLayers.osmCarto) + this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto) } - )//*/ + ) + const self = this + + function setLayerCategory(category: EliCategory) { + const available = self.availableLayers.data + const matchingCategoryLayers = available.filter( + (l) => l.properties.category === category + ) + const best = + matchingCategoryLayers.find((l) => l.properties.best) ?? matchingCategoryLayers[0] + const rasterLayer = self.mapProperties.rasterLayer + if (rasterLayer.data !== best) { + rasterLayer.setData(best) + return + } + + // The current layer is already selected... + // We switch the layers again + + if (category === "osmbasedmap") { + rasterLayer.setData(undefined) + } else { + // search the _second_ best layer + const secondbest = matchingCategoryLayers.find( + (l) => l.properties.best && l !== best + ) + const secondNotBest = matchingCategoryLayers.find((l) => l !== best) + rasterLayer.setData(secondbest ?? secondNotBest) + } + } + + Hotkeys.RegisterHotkey( + { nomod: "O" }, + Translations.t.hotkeyDocumentation.selectOsmbasedmap, + () => setLayerCategory("osmbasedmap") + ) + + Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () => + setLayerCategory("map") + ) + + Hotkeys.RegisterHotkey( + { nomod: "P" }, + Translations.t.hotkeyDocumentation.selectAerial, + () => setLayerCategory("photo") + ) } /** diff --git a/langs/ca.json b/langs/ca.json index d2b7381c6d..b4a7c3f409 100644 --- a/langs/ca.json +++ b/langs/ca.json @@ -362,7 +362,6 @@ "intro": "MapComplete admet les tecles següents:", "key": "Combinació de tecles", "openLayersPanel": "Obre el panell de fons, capes i filtres", - "selectBackground": "Seleccioneu una capa de fons de la categoria {category}", "selectMapnik": "Estableix la capa de fons OpenStreetMap-carto", "selectSearch": "Seleccioneu la barra de cerca per cercar ubicacions", "title": "Dreceres" diff --git a/langs/cs.json b/langs/cs.json index 48fad68b12..ddccbedb9a 100644 --- a/langs/cs.json +++ b/langs/cs.json @@ -207,7 +207,6 @@ "intro": "MapComplete podporuje následující klávesy:", "key": "Kombinace kláves", "openLayersPanel": "Otevře panel Pozadí, vrstvy a filtry", - "selectBackground": "Vybrat vrstvu pozadí kategorie {category}", "selectMapnik": "Nastaví vrstvu pozadí na OpenStreetMap-carto", "selectSearch": "Vybere vyhledávací řádek pro vyhledávání míst", "title": "Klávesové zkratky" diff --git a/langs/de.json b/langs/de.json index 0931ed5aff..2b56ad03ae 100644 --- a/langs/de.json +++ b/langs/de.json @@ -362,7 +362,6 @@ "intro": "MapComplete unterstützt folgende Tastaturbefehle:", "key": "Tastenkombination", "openLayersPanel": "Öffnet das Bedienfeld Hintergrund, Ebenen und Filter", - "selectBackground": "Wählen Sie eine Hintergrundebene der Kategorie {category} aus", "selectMapnik": "Setzt die Hintergrundebene auf OpenStreetMap-carto", "selectSearch": "Suchleiste auswählen, um nach Orten zu suchen", "title": "Tastaturbefehle" diff --git a/langs/en.json b/langs/en.json index dc34045bc0..eceb2a6aa1 100644 --- a/langs/en.json +++ b/langs/en.json @@ -386,8 +386,10 @@ "intro": "MapComplete supports the following keys:", "key": "Key combination", "openLayersPanel": "Opens the Background, layers and filters panel", - "selectBackground": "Select a background layer of category {category}", - "selectMapnik": "Sets the background layer to OpenStreetMap-carto", + "selectAerial": "Set the background to aerial or satellite imagery. Toggles between the two best, available layers", + "selectMap": "Set the background to a map from external sources. Toggles between the two best, available layers", + "selectMapnik": "Set the background layer to OpenStreetMap-carto", + "selectOsmbasedmap": "Set the background layer to on OpenStreetMap-based map (or disable the background raster layer)", "selectSearch": "Select the search bar to search locations", "title": "Hotkeys" }, diff --git a/langs/fr.json b/langs/fr.json index 15b2c80d81..09455c6c96 100644 --- a/langs/fr.json +++ b/langs/fr.json @@ -311,7 +311,6 @@ "closeSidebar": "Fermer la barre latérale", "key": "Combinaison de touches", "openLayersPanel": "Ouvre le panneau fond-de-plan, couches et filtres", - "selectBackground": "Sélectionnez un fond de carte de type {category}", "selectMapnik": "Appliquer le fond de carte OpenStreetMap-carto", "selectSearch": "Sélectionner la barre de recherche de lieux", "title": "Raccourcis clavier" diff --git a/langs/nl.json b/langs/nl.json index 3635b3c948..e9f5f26b27 100644 --- a/langs/nl.json +++ b/langs/nl.json @@ -362,7 +362,6 @@ "intro": "MapComplete ondersteunt de volgende sneltoetsen:", "key": "Toets-combinatie", "openLayersPanel": "Open het paneel met lagen, filters en achtergrondkaart", - "selectBackground": "Selecteer een achtergrondlaag van category {category}", "selectMapnik": "Selecteer OpenStreetMap-carto als achtergrondlaag", "selectSearch": "Selecteer de zoekbalk om locaties te zoeken", "title": "Sneltoetsen" From 6d8e5d4363dadb95a0f526bae90661d28ffe9ecf Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 21 Apr 2023 17:35:46 +0200 Subject: [PATCH 059/257] Refactoring: cleanup of index.css file --- UI/Image/ImageUploadFlow.ts | 8 ++++-- index.css | 57 ++++++------------------------------- 2 files changed, 13 insertions(+), 52 deletions(-) diff --git a/UI/Image/ImageUploadFlow.ts b/UI/Image/ImageUploadFlow.ts index 6d6c139527..16ae1c0173 100644 --- a/UI/Image/ImageUploadFlow.ts +++ b/UI/Image/ImageUploadFlow.ts @@ -70,9 +70,11 @@ export class ImageUploadFlow extends Toggle { const label = new Combine([ Svg.camera_plus_ui().SetClass("block w-12 h-12 p-1 text-4xl "), labelContent, - ]).SetClass( - "p-2 border-4 border-detail rounded-full font-bold h-full align-middle w-full flex justify-center" - ) + ]) + .SetClass( + "p-2 border-4 border-detail rounded-full font-bold h-full align-middle w-full flex justify-center" + ) + .SetStyle(" border-color: var(--foreground-color);") const licenseStore = state?.osmConnection?.GetPreference( Constants.OsmPreferenceKeyPicturesLicense, "CC0" diff --git a/index.css b/index.css index 38fac7a144..880d448942 100644 --- a/index.css +++ b/index.css @@ -111,56 +111,27 @@ img { height: 100%; } -.titleicon img { - width: unset; -} - -.titleicon svg { - width: unset; -} -.svg-catch svg path { - fill: var(--catch-detail-color) !important; - stroke: var(--catch-detail-color) !important; -} - -.svg-unsubtle svg path { - fill: var(--unsubtle-detail-color) !important; - stroke: var(--unsubtle-detail-color) !important; -} - -.svg-subtle svg path { - fill: var(--subtle-detail-color) !important; - stroke: var(--subtle-detail-color) !important; -} - -.svg-foreground svg path { - fill: var(--foreground-color) !important; - stroke: var(--foreground-color) !important; -} - .no-images img { + /* Used solely in 'imageAttribution' */ display: none; } +.text-white a { + /* Used solely in 'imageAttribution' */ + color: var(--background-color); +} + + + .weblate-link { /* Weblate-links are the little translation icon next to translatable sentences. Due to their special nature, they are exempt from some rules */ } -.mapcontrol svg path { - fill: var(--subtle-detail-color-contrast) !important; -} - -.red-svg svg path { - stroke: #d71010 !important; -} a { color: var(--foreground-color); } -.text-white a { - color: var(--background-color); -} .btn { line-height: 1.25rem; @@ -204,10 +175,6 @@ a { border: 3px solid var(--unsubtle-detail-color); } -.h-min { - height: min-content; -} - /* slider */ input[type="range"].vertical { writing-mode: bt-lr; /* IE */ @@ -232,14 +199,6 @@ input[type="range"].vertical { } } -.border-detail { - border-color: var(--foreground-color); -} - -.w-min { - width: min-content; -} - .rounded-left-full { border-bottom-left-radius: 999rem; border-top-left-radius: 999rem; From c2b9a81c5142dbf7f2fcfce4a166030591e6067e Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 21 Apr 2023 17:37:36 +0200 Subject: [PATCH 060/257] Refactoring: don't show title icons if they are not known --- UI/BigComponents/SelectedElementView.svelte | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/UI/BigComponents/SelectedElementView.svelte b/UI/BigComponents/SelectedElementView.svelte index 74b9415bde..e5607975d2 100644 --- a/UI/BigComponents/SelectedElementView.svelte +++ b/UI/BigComponents/SelectedElementView.svelte @@ -20,6 +20,7 @@ onDestroy(tags.addCallbackAndRun(tags => { _tags = tags; })); + console.log(layer.titleIcons.map(tr => tr.id)); let _metatags: Record; onDestroy(state.userRelatedState.preferencesAsTags.addCallbackAndRun(tags => { @@ -39,10 +40,13 @@
- {#each layer.titleIcons as titleIconConfig (titleIconConfig.id)} -
- -
+ {#each layer.titleIcons as titleIconConfig} + {#if titleIconConfig.IsKnown(_tags)} +
+ +
+ {/if} {/each}
From 23b26c4197b926c848ae360347f54277a0bd5e66 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 21 Apr 2023 17:37:50 +0200 Subject: [PATCH 061/257] Refactoring: fix background layer switch and hotkeys --- Logic/Actors/BackgroundLayerResetter.ts | 8 +++- Logic/MetaTagging.ts | 2 +- Models/RasterLayers.ts | 56 +++++++++++-------------- Models/ThemeViewState.ts | 40 ++++++------------ UI/Map/MapLibreAdaptor.ts | 2 +- 5 files changed, 44 insertions(+), 64 deletions(-) diff --git a/Logic/Actors/BackgroundLayerResetter.ts b/Logic/Actors/BackgroundLayerResetter.ts index f2bf154d3b..30a0fbc16a 100644 --- a/Logic/Actors/BackgroundLayerResetter.ts +++ b/Logic/Actors/BackgroundLayerResetter.ts @@ -23,20 +23,24 @@ export default class BackgroundLayerResetter { availableLayers.addCallbackAndRunD((availableLayers) => { // We only check on move/on change of the availableLayers const currentBgPolygon: RasterLayerPolygon | undefined = currentBackgroundLayer.data + if (currentBackgroundLayer === undefined) { + return + } if (availableLayers.findIndex((available) => currentBgPolygon == available) >= 0) { // Still available! return } + console.log("Current layer properties:", currentBgPolygon) // Oops, we panned out of range for this layer! // What is the 'best' map of the same category which is available? const availableInSameCat = RasterLayerUtils.SelectBestLayerAccordingTo( availableLayers, - currentBgPolygon?.properties?.category ?? "osmbasedmap" + currentBgPolygon?.properties?.category ) console.log("Selecting a different layer:", availableInSameCat.properties.id) - currentBackgroundLayer.setData(availableInSameCat ?? AvailableRasterLayers.osmCarto) + currentBackgroundLayer.setData(availableInSameCat) }) } } diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index b8ff65993a..15f10e4ab1 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -71,7 +71,7 @@ export default class MetaTagging { return } - console.trace("Recalculating metatags...") + console.debug("Recalculating metatags...") const metatagsToApply: SimpleMetaTagger[] = [] for (const metatag of SimpleMetaTaggers.metatags) { if (metatag.includesDates) { diff --git a/Models/RasterLayers.ts b/Models/RasterLayers.ts index 0602015f7b..57e5274978 100644 --- a/Models/RasterLayers.ts +++ b/Models/RasterLayers.ts @@ -45,6 +45,7 @@ export class AvailableRasterLayers { }, geometry: BBox.global.asGeometry(), } + public static layersAvailableAt( location: Store<{ lon: number; lat: number }> ): Store { @@ -77,44 +78,35 @@ export class AvailableRasterLayers { } export class RasterLayerUtils { + /** + * Selects, from the given list of available rasterLayerPolygons, a rasterLayer. + * This rasterlayer will be of type 'preferredCategory' and will be of the 'best'-layer (if available). + * Returns 'undefined' if no such layer is available + * @param available + * @param preferredCategory + * @param ignoreLayer + */ public static SelectBestLayerAccordingTo( available: RasterLayerPolygon[], - preferredCategory: string | string[] + preferredCategory: string, + ignoreLayer?: RasterLayerPolygon ): RasterLayerPolygon { - available = [...available] - - if (preferredCategory === undefined) { - return available[0] - } - - let prefered: string[] - if (typeof preferredCategory === "string") { - prefered = [preferredCategory] - } else { - prefered = preferredCategory - } - - for (let i = prefered.length - 1; i >= 0; i--) { - const category = prefered[i] - //Then sort all layers of the preferred type to the top. Stability of the sorting will force a 'best' photo layer on top - available.sort((ap, bp) => { - const a = ap.properties - const b = bp.properties - if (a.category === category && b.category === category) { - return 0 + let secondBest: RasterLayerPolygon = undefined + for (const rasterLayer of available) { + if (rasterLayer === ignoreLayer) { + continue + } + const p = rasterLayer.properties + if (p.category === preferredCategory) { + if (p.best) { + return rasterLayer } - if (a.category !== category) { - return 1 + if (!secondBest) { + secondBest = rasterLayer } - - return -1 - }) + } } - const best = available.find((l) => l.properties.best) - if (best) { - return best - } - return available[0] + return secondBest } } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 53afde28d9..53b8099aeb 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -21,7 +21,7 @@ import { QueryParameters } from "../Logic/Web/QueryParameters" import UserRelatedState from "../Logic/State/UserRelatedState" import LayerConfig from "./ThemeConfig/LayerConfig" import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" -import { AvailableRasterLayers, RasterLayerPolygon } from "./RasterLayers" +import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers" import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource" import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore" @@ -46,6 +46,7 @@ import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer" import { Utils } from "../Utils" import { EliCategory } from "./RasterLayerProperties" +import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter" /** * @@ -300,34 +301,16 @@ export default class ThemeViewState implements SpecialVisualizationState { this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto) } ) - const self = this - - function setLayerCategory(category: EliCategory) { - const available = self.availableLayers.data - const matchingCategoryLayers = available.filter( - (l) => l.properties.category === category + const setLayerCategory = (category: EliCategory) => { + const available = this.availableLayers.data + const current = this.mapProperties.rasterLayer + const best = RasterLayerUtils.SelectBestLayerAccordingTo( + available, + category, + current.data ) - const best = - matchingCategoryLayers.find((l) => l.properties.best) ?? matchingCategoryLayers[0] - const rasterLayer = self.mapProperties.rasterLayer - if (rasterLayer.data !== best) { - rasterLayer.setData(best) - return - } - - // The current layer is already selected... - // We switch the layers again - - if (category === "osmbasedmap") { - rasterLayer.setData(undefined) - } else { - // search the _second_ best layer - const secondbest = matchingCategoryLayers.find( - (l) => l.properties.best && l !== best - ) - const secondNotBest = matchingCategoryLayers.find((l) => l !== best) - rasterLayer.setData(secondbest ?? secondNotBest) - } + console.log("Best layer for category", category, "is", best.properties.id) + current.setData(best) } Hotkeys.RegisterHotkey( @@ -454,5 +437,6 @@ export default class ThemeViewState implements SpecialVisualizationState { new ChangeToElementsActor(this.changes, this.featureProperties) new PendingChangesUploader(this.changes, this.selectedElement) new SelectedElementTagsUpdater(this) + new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers) } } diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts index dd618d912a..f989f164a1 100644 --- a/UI/Map/MapLibreAdaptor.ts +++ b/UI/Map/MapLibreAdaptor.ts @@ -343,7 +343,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { // already the correct background layer, nothing to do return } - await this.awaitStyleIsLoaded() + // await this.awaitStyleIsLoaded() if (background !== this.rasterLayer?.data?.properties) { // User selected another background in the meantime... abort From 4548689a284cb186231223b55181dc80b6815842 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 21 Apr 2023 17:43:41 +0200 Subject: [PATCH 062/257] Refactoring: More cleanup of index.css --- UI/BaseUIElement.ts | 1 - css/index-tailwind-output.css | 246 ++-------------------------------- index.css | 62 --------- 3 files changed, 10 insertions(+), 299 deletions(-) diff --git a/UI/BaseUIElement.ts b/UI/BaseUIElement.ts index 218be172ba..37d651c302 100644 --- a/UI/BaseUIElement.ts +++ b/UI/BaseUIElement.ts @@ -14,7 +14,6 @@ export default abstract class BaseUIElement { public onClick(f: () => void) { this._onClick = f - this.SetClass("clickable") if (this._constructedHtmlElement !== undefined) { this._constructedHtmlElement.onclick = f } diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index c0df2b84b6..8d01e38753 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -755,14 +755,6 @@ video { isolation: isolate; } -.z-10 { - z-index: 10; -} - -.z-0 { - z-index: 0; -} - .float-right { float: right; } @@ -945,10 +937,6 @@ video { margin-top: -3rem; } -.mb-0 { - margin-bottom: 0px; -} - .box-border { box-sizing: border-box; } @@ -1045,14 +1033,6 @@ video { height: 16rem; } -.h-96 { - height: 24rem; -} - -.h-0 { - height: 0px; -} - .h-48 { height: 12rem; } @@ -1065,10 +1045,6 @@ video { height: 20rem; } -.max-h-20vh { - max-height: 20vh; -} - .max-h-32 { max-height: 8rem; } @@ -1172,10 +1148,6 @@ video { flex: none; } -.flex-shrink-0 { - flex-shrink: 0; -} - .flex-shrink { flex-shrink: 1; } @@ -1274,10 +1246,6 @@ video { place-content: center; } -.content-center { - align-content: center; -} - .content-start { align-content: flex-start; } @@ -1340,10 +1308,6 @@ video { overflow: scroll; } -.overflow-y-auto { - overflow-y: auto; -} - .truncate { overflow: hidden; text-overflow: ellipsis; @@ -1514,11 +1478,6 @@ video { background-color: rgb(224 231 255 / var(--tw-bg-opacity)); } -.bg-gray-100 { - --tw-bg-opacity: 1; - background-color: rgb(243 244 246 / var(--tw-bg-opacity)); -} - .bg-gray-300 { --tw-bg-opacity: 1; background-color: rgb(209 213 219 / var(--tw-bg-opacity)); @@ -1550,14 +1509,14 @@ video { padding: 0.75rem; } -.p-0 { - padding: 0px; -} - .p-0\.5 { padding: 0.125rem; } +.p-0 { + padding: 0px; +} + .p-8 { padding: 2rem; } @@ -1593,18 +1552,6 @@ video { padding-left: 0.5rem; } -.pb-20 { - padding-bottom: 5rem; -} - -.pt-1 { - padding-top: 0.25rem; -} - -.pb-1 { - padding-bottom: 0.25rem; -} - .pr-2 { padding-right: 0.5rem; } @@ -1649,18 +1596,10 @@ video { padding-right: 0.25rem; } -.pb-4 { - padding-bottom: 1rem; -} - .pb-2 { padding-bottom: 0.5rem; } -.pl-6 { - padding-left: 1.5rem; -} - .text-center { text-align: center; } @@ -1866,12 +1805,6 @@ video { transition-duration: 150ms; } -.transition-opacity { - transition-property: opacity; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 150ms; -} - .ease-in-out { transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } @@ -1880,10 +1813,6 @@ video { z-index: 10000; } -.z-above-controls { - z-index: 10001; -} - .bg-subtle { background-color: var(--subtle-detail-color); color: var(--subtle-detail-color-contrast); @@ -1949,58 +1878,24 @@ img { height: 100%; } -.titleicon img { - width: unset; -} - -.titleicon svg { - width: unset; -} - -.svg-catch svg path { - fill: var(--catch-detail-color) !important; - stroke: var(--catch-detail-color) !important; -} - -.svg-unsubtle svg path { - fill: var(--unsubtle-detail-color) !important; - stroke: var(--unsubtle-detail-color) !important; -} - -.svg-subtle svg path { - fill: var(--subtle-detail-color) !important; - stroke: var(--subtle-detail-color) !important; -} - -.svg-foreground svg path { - fill: var(--foreground-color) !important; - stroke: var(--foreground-color) !important; -} - .no-images img { + /* Used solely in 'imageAttribution' */ display: none; } +.text-white a { + /* Used solely in 'imageAttribution' */ + color: var(--background-color); +} + .weblate-link { /* Weblate-links are the little translation icon next to translatable sentences. Due to their special nature, they are exempt from some rules */ } -.mapcontrol svg path { - fill: var(--subtle-detail-color-contrast) !important; -} - -.red-svg svg path { - stroke: #d71010 !important; -} - a { color: var(--foreground-color); } -.text-white a { - color: var(--background-color); -} - .btn { line-height: 1.25rem; --tw-text-opacity: 1; @@ -2046,11 +1941,6 @@ a { border: 3px solid var(--unsubtle-detail-color); } -.h-min { - height: -webkit-min-content; - height: min-content; -} - /* slider */ input[type="range"].vertical { @@ -2079,15 +1969,6 @@ input[type="range"].vertical { } } -.border-detail { - border-color: var(--foreground-color); -} - -.w-min { - width: -webkit-min-content; - width: min-content; -} - .rounded-left-full { border-bottom-left-radius: 999rem; border-top-left-radius: 999rem; @@ -2098,18 +1979,6 @@ input[type="range"].vertical { border-top-right-radius: 999rem; } -.w-16-imp { - width: 4rem !important; -} - -.w-32-imp { - width: 8rem !important; -} - -.w-48-imp { - width: 12rem !important; -} - .link-underline a { -webkit-text-decoration: underline 1px var(--foreground-color); text-decoration: underline 1px var(--foreground-color); @@ -2171,10 +2040,6 @@ li::marker { border: 5px solid var(--catch-detail-color); } -.border-invisible { - border: 5px solid #00000000; -} - .border-attention { border-color: var(--catch-detail-color); } @@ -2183,23 +2048,6 @@ li::marker { fill: var(--catch-detail-color) !important; } -.single-layer-selection-toggle { - position: relative; - width: 2em; - height: 2em; - flex-shrink: 0; -} - -.single-layer-selection-toggle img { - max-height: 2em !important; - max-width: 2em !important; -} - -.single-layer-selection-toggle svg { - max-height: 2em !important; - max-width: 2em !important; -} - .block-ruby { display: block ruby; } @@ -2278,14 +2126,6 @@ li::marker { padding: 0.15em 0.3em; } -.clickable { - pointer-events: all; -} - -.unclickable { - pointer-events: none !important; -} - @-webkit-keyframes slide { /* This is the animation on the marker to add a new point - it slides through all the possible presets */ @@ -2320,13 +2160,6 @@ input { color: var(--foreground-color); } -.floating-element-width { - max-width: calc(100vw - 5em); - width: 40em; -} - -/****** ShareScreen *****/ - .literal-code { display: inline-block; background-color: lightgray; @@ -2336,15 +2169,6 @@ input { box-sizing: border-box; } -.code { - display: inline-block; - background-color: lightgray; - padding: 0.5em; - word-break: break-word; - color: black; - box-sizing: border-box; -} - /** Switch layout **/ .small-image img { @@ -2371,10 +2195,6 @@ input { background-color: #f2f2f2; } -.layer-toggle { - /* The checkbox that toggles a single layer */ -} - .glowing-shadow { -webkit-animation: glowing 1s ease-in-out infinite alternate; animation: glowing 1s ease-in-out infinite alternate; @@ -2451,10 +2271,6 @@ input { color: rgb(30 64 175 / var(--tw-text-opacity)); } -.hover\:opacity-100:hover { - opacity: 1; -} - .hover\:shadow-xl:hover { --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); @@ -2532,11 +2348,6 @@ input { text-align: center; } - .sm\:text-xl { - font-size: 1.25rem; - line-height: 1.75rem; - } - .sm\:text-5xl { font-size: 3rem; line-height: 1; @@ -2549,10 +2360,6 @@ input { } @media (min-width: 768px) { - .md\:relative { - position: relative; - } - .md\:m-1 { margin: 0.25rem; } @@ -2573,10 +2380,6 @@ input { margin-top: 1rem; } - .md\:block { - display: block; - } - .md\:flex { display: flex; } @@ -2585,10 +2388,6 @@ input { display: grid; } - .md\:hidden { - display: none; - } - .md\:h-12 { height: 3rem; } @@ -2601,10 +2400,6 @@ input { width: 50%; } - .md\:w-auto { - width: auto; - } - .md\:grid-flow-row { grid-auto-flow: row; } @@ -2613,10 +2408,6 @@ input { grid-template-columns: repeat(2, minmax(0, 1fr)); } - .md\:rounded-xl { - border-radius: 0.75rem; - } - .md\:border-t-2 { border-top-width: 2px; } @@ -2629,23 +2420,6 @@ input { padding: 0.75rem; } - .md\:pt-4 { - padding-top: 1rem; - } - - .md\:pt-0 { - padding-top: 0px; - } - - .md\:pb-0 { - padding-bottom: 0px; - } - - .md\:text-2xl { - font-size: 1.5rem; - line-height: 2rem; - } - .md\:text-6xl { font-size: 3.75rem; line-height: 1; diff --git a/index.css b/index.css index 880d448942..a98dc2916c 100644 --- a/index.css +++ b/index.css @@ -209,18 +209,6 @@ input[type="range"].vertical { border-top-right-radius: 999rem; } -.w-16-imp { - width: 4rem !important; -} - -.w-32-imp { - width: 8rem !important; -} - -.w-48-imp { - width: 12rem !important; -} - .link-underline a { text-decoration: underline 1px var(--foreground-color); } @@ -286,10 +274,6 @@ li::marker { border: 5px solid var(--catch-detail-color); } -.border-invisible { - border: 5px solid #00000000; -} - .border-attention { border-color: var(--catch-detail-color); } @@ -298,24 +282,6 @@ li::marker { fill: var(--catch-detail-color) !important; } - -.single-layer-selection-toggle { - position: relative; - width: 2em; - height: 2em; - flex-shrink: 0; -} - -.single-layer-selection-toggle img { - max-height: 2em !important; - max-width: 2em !important; -} - -.single-layer-selection-toggle svg { - max-height: 2em !important; - max-width: 2em !important; -} - .block-ruby { display: block ruby; } @@ -392,14 +358,6 @@ li::marker { padding: 0.15em 0.3em; } -.clickable { - pointer-events: all; -} - -.unclickable { - pointer-events: none !important; -} - @keyframes slide { /* This is the animation on the marker to add a new point - it slides through all the possible presets */ from { @@ -419,14 +377,6 @@ input { color: var(--foreground-color); } -.floating-element-width { - max-width: calc(100vw - 5em); - width: 40em; -} - - -/****** ShareScreen *****/ - .literal-code { display: inline-block; background-color: lightgray; @@ -436,15 +386,6 @@ input { box-sizing: border-box; } -.code { - display: inline-block; - background-color: lightgray; - padding: 0.5em; - word-break: break-word; - color: black; - box-sizing: border-box; -} - /** Switch layout **/ .small-image img { height: 1em; @@ -470,9 +411,6 @@ input { background-color: #f2f2f2; } -.layer-toggle { - /* The checkbox that toggles a single layer */ -} .glowing-shadow { -webkit-animation: glowing 1s ease-in-out infinite alternate; From 59544ec073ae9631ba00c97c5f5ee63fffa2c49b Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 21 Apr 2023 18:09:14 +0200 Subject: [PATCH 063/257] Refactoring: more css cleanup --- Models/ThemeConfig/Conversion/PrepareLayer.ts | 1 + UI/Base/FromHtml.svelte | 4 +- UI/BaseUIElement.ts | 1 + UI/Wikipedia/WikipediaArticle.svelte | 14 ++++--- css/tagrendering.css | 5 +-- css/userbadge.css | 39 ------------------- index.html | 1 - test.html | 1 - 8 files changed, 13 insertions(+), 53 deletions(-) delete mode 100644 css/userbadge.css diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index 19c1d380b4..fbb69c35c7 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -632,6 +632,7 @@ export class AddEditingElements extends DesugaringStep { const trc: TagRenderingConfigJson = { id: "all-tags", render: { "*": "{all_tags()}" }, + metacondition: { or: [ "__featureSwitchIsTesting=true", diff --git a/UI/Base/FromHtml.svelte b/UI/Base/FromHtml.svelte index 5202cbf875..f947b9de35 100644 --- a/UI/Base/FromHtml.svelte +++ b/UI/Base/FromHtml.svelte @@ -6,8 +6,8 @@ export let src: string; let htmlElem: HTMLElement; $: { - if(htmlElem){ - htmlElem.innerHTML = src + if (htmlElem) { + htmlElem.innerHTML = src; } } diff --git a/UI/BaseUIElement.ts b/UI/BaseUIElement.ts index 37d651c302..94a9764763 100644 --- a/UI/BaseUIElement.ts +++ b/UI/BaseUIElement.ts @@ -14,6 +14,7 @@ export default abstract class BaseUIElement { public onClick(f: () => void) { this._onClick = f + this.SetClass("cursor-pointer") if (this._constructedHtmlElement !== undefined) { this._constructedHtmlElement.onclick = f } diff --git a/UI/Wikipedia/WikipediaArticle.svelte b/UI/Wikipedia/WikipediaArticle.svelte index e7067b4b80..cea30593c8 100644 --- a/UI/Wikipedia/WikipediaArticle.svelte +++ b/UI/Wikipedia/WikipediaArticle.svelte @@ -16,9 +16,9 @@ export let wikipediaDetails: Store; - - - + + + {#if $wikipediaDetails.wikidata} @@ -26,15 +26,16 @@ {/if} {#if $wikipediaDetails.firstParagraph === "" || $wikipediaDetails.firstParagraph === undefined} - - + + {:else} + - + Read the rest of the article @@ -43,4 +44,5 @@ + {/if} diff --git a/css/tagrendering.css b/css/tagrendering.css index 417c4c62f0..09634d2e9d 100644 --- a/css/tagrendering.css +++ b/css/tagrendering.css @@ -36,10 +36,7 @@ max-width: 100%; } -.question-subtext { - font-size: medium; - font-weight: normal; -} + .question-option-with-border { border: 2px solid lightgray; diff --git a/css/userbadge.css b/css/userbadge.css deleted file mode 100644 index 3b272cc48b..0000000000 --- a/css/userbadge.css +++ /dev/null @@ -1,39 +0,0 @@ - -.userstats { - display: flex; - align-items: center; - margin-top: 0.2em; - margin-bottom: 0.2em; -} - -.userstats a { - display: block ruby; - padding-right: 0.2em; - margin-bottom: 0.2em; -} - -.userstats span { - display: block ruby; - padding-right: 0.2em; - margin-bottom: 0.2em; -} - -.userstats svg { - width: 1em; - height: 1em; - border-radius: 0; - display: block; -} - -.userstats img { - width: 1em; - height: 1em; - border-radius: 0; - display: block; -} - -.userbadge-login { - background-color: var(--subtle-detail-color) !important; - color: var(--subtle-detail-color-contrast); - height: 3em; -} diff --git a/index.html b/index.html index e2e5553463..6cc4bd75c5 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,6 @@ - diff --git a/test.html b/test.html index a84eca7e20..2a5d2478c9 100644 --- a/test.html +++ b/test.html @@ -4,7 +4,6 @@ Small tests - From 476423f9d168c366eac48145dc80527151369a5d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 21 Apr 2023 20:50:38 +0200 Subject: [PATCH 064/257] Refactoring: re-enable caching --- Logic/FeatureSource/Actors/TileLocalStorage.ts | 4 ++++ Logic/UIEventSource.ts | 6 ++++++ Models/ThemeViewState.ts | 5 +++-- UI/BaseUIElement.ts | 2 +- UI/InputElement/Validator.ts | 5 +++++ 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Logic/FeatureSource/Actors/TileLocalStorage.ts b/Logic/FeatureSource/Actors/TileLocalStorage.ts index dc92acf855..22e7dbb031 100644 --- a/Logic/FeatureSource/Actors/TileLocalStorage.ts +++ b/Logic/FeatureSource/Actors/TileLocalStorage.ts @@ -11,6 +11,7 @@ import { UIEventSource } from "../../UIEventSource" export default class TileLocalStorage { private static perLayer: Record> = {} private readonly _layername: string + private readonly inUse = new UIEventSource(false) private readonly cachedSources: Record & { flush: () => void }> = {} private constructor(layername: string) { @@ -49,7 +50,10 @@ export default class TileLocalStorage { private async SetIdb(tileIndex: number, data: any): Promise { try { + await this.inUse.AsPromise((inUse) => !inUse) + this.inUse.setData(true) await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data) + this.inUse.setData(false) } catch (e) { console.error( "Could not save tile to indexed-db: ", diff --git a/Logic/UIEventSource.ts b/Logic/UIEventSource.ts index 4a16550fff..6aa823055c 100644 --- a/Logic/UIEventSource.ts +++ b/Logic/UIEventSource.ts @@ -240,6 +240,12 @@ export abstract class Store implements Readable { return newSource } + /** + * Converts the uiEventSource into a promise. + * The promise will return the value of the store if the given condition evaluates to true + * @param condition: an optional condition, default to 'store.value !== undefined' + * @constructor + */ public AsPromise(condition?: (t: T) => boolean): Promise { const self = this condition = condition ?? ((t) => t !== undefined) diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 53b8099aeb..5f71ce78e5 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -47,6 +47,7 @@ import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer" import { Utils } from "../Utils" import { EliCategory } from "./RasterLayerProperties" import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter" +import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" /** * @@ -204,13 +205,13 @@ export default class ThemeViewState implements SpecialVisualizationState { this.perLayer = perLayer.perLayer } this.perLayer.forEach((fs) => { - /* TODO enable new SaveFeatureSourceToLocalStorage( + new SaveFeatureSourceToLocalStorage( this.osmConnection.Backend(), fs.layer.layerDef.id, 15, fs, this.featureProperties - )//*/ + ) const filtered = new FilteringFeatureSource( fs.layer, diff --git a/UI/BaseUIElement.ts b/UI/BaseUIElement.ts index 94a9764763..601218a91e 100644 --- a/UI/BaseUIElement.ts +++ b/UI/BaseUIElement.ts @@ -181,7 +181,7 @@ export default abstract class BaseUIElement { // @ts-ignore e.consumed = true } - el.classList.add("pointer-events-none", "cursor-pointer") + el.classList.add("cursor-pointer") } return el diff --git a/UI/InputElement/Validator.ts b/UI/InputElement/Validator.ts index 858622ebe8..5ad93b8829 100644 --- a/UI/InputElement/Validator.ts +++ b/UI/InputElement/Validator.ts @@ -57,6 +57,11 @@ export abstract class Validator { return true } + /** + * Reformats for the human + * @param s + * @param country + */ public reformat(s: string, country?: () => string): string { return s } From 8a40b9cbe65e318c1cab606ead678fdd7ac2706c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 23 Apr 2023 13:14:54 +0200 Subject: [PATCH 065/257] Refactoring: include special layers in the indexes --- Models/ThemeViewState.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 5f71ce78e5..de712afd05 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -406,8 +406,6 @@ export default class ThemeViewState implements SpecialVisualizationState { .get("range") ?.isDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true) - // The following layers are _not_ indexed; they trigger to much and thus trigger the metatagging - const dontInclude = new Set(["gps_location", "gps_location_history", "gps_track"]) this.layerState.filteredLayers.forEach((flayer) => { const id = flayer.layerDef.id const features: FeatureSource = specialLayers[id] @@ -415,10 +413,8 @@ export default class ThemeViewState implements SpecialVisualizationState { return } - if (!dontInclude.has(id)) { - this.featureProperties.trackFeatureSource(features) - this.indexedFeatures.addSource(features) - } + this.featureProperties.trackFeatureSource(features) + // this.indexedFeatures.addSource(features) new ShowDataLayer(this.map, { features, doShowLayer: flayer.isDisplayed, From 652d617583c1be8b140fd8bc06059c20f09d2547 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 24 Apr 2023 02:27:55 +0200 Subject: [PATCH 066/257] Styling: theme view buttons --- Models/MenuState.ts | 11 ++ UI/Base/Hotkeys.ts | 2 +- UI/Base/MapControlButton.svelte | 4 +- UI/BigComponents/Geosearch.svelte | 4 +- UI/DefaultGuiState.ts | 2 +- UI/ThemeViewGUI.svelte | 53 +++++---- assets/layers/recycling/recycling.json | 11 +- css/index-tailwind-output.css | 153 ++++++++++++++++++------- 8 files changed, 160 insertions(+), 80 deletions(-) diff --git a/Models/MenuState.ts b/Models/MenuState.ts index 43d3146181..40ecff5354 100644 --- a/Models/MenuState.ts +++ b/Models/MenuState.ts @@ -54,6 +54,17 @@ export class MenuState { this.highlightedLayerInFilters.setData(undefined) } }) + + this.menuIsOpened.addCallbackAndRunD((opened) => { + if (opened) { + this.themeIsOpened.setData(false) + } + }) + this.themeIsOpened.addCallbackAndRunD((opened) => { + if (opened) { + this.menuIsOpened.setData(false) + } + }) } public openFilterView(highlightLayer?: LayerConfig | string) { this.themeIsOpened.setData(true) diff --git a/UI/Base/Hotkeys.ts b/UI/Base/Hotkeys.ts index b260705cf9..6b4f073650 100644 --- a/UI/Base/Hotkeys.ts +++ b/UI/Base/Hotkeys.ts @@ -123,7 +123,7 @@ export default class Hotkeys { new Table( [t.key, t.action], byKey.map(([key, doc]) => { - return [new FixedUiElement(key).SetClass("code"), doc] + return [new FixedUiElement(key).SetClass("literal-code"), doc] }) ), ]) diff --git a/UI/Base/MapControlButton.svelte b/UI/Base/MapControlButton.svelte index 4508a98ae2..915d8cf7b0 100644 --- a/UI/Base/MapControlButton.svelte +++ b/UI/Base/MapControlButton.svelte @@ -8,6 +8,6 @@ -
dispatch("click", e)} class="subtle-background rounded-full min-w-10 w-fit h-10 m-0.5 md:m-1 p-1 cursor-pointer"> - +
dispatch("click", e)} class="subtle-background rounded-full h-fit w-fit m-0.5 md:m-1 p-0.5 sm:p-1 cursor-pointer"> +
diff --git a/UI/BigComponents/Geosearch.svelte b/UI/BigComponents/Geosearch.svelte index 10965d13f1..aa639a044f 100644 --- a/UI/BigComponents/Geosearch.svelte +++ b/UI/BigComponents/Geosearch.svelte @@ -69,7 +69,7 @@ -
+
{#if isRunning} @@ -89,7 +89,7 @@ {/if}
-
+
diff --git a/UI/DefaultGuiState.ts b/UI/DefaultGuiState.ts index 48b9ad71f5..c0f4094d05 100644 --- a/UI/DefaultGuiState.ts +++ b/UI/DefaultGuiState.ts @@ -48,7 +48,7 @@ export class DefaultGuiState { } } - public closeAll() { + public closeAll(except) { for (const sourceKey in this.sources) { this.sources[sourceKey].setData(false) } diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 10b45698c7..b34655ce6e 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -64,27 +64,37 @@ -
+
-
- state.guistate.themeIsOpened.setData(true)}> -
- - - - +
+ + +
+
- - state.guistate.menuIsOpened.setData(true)}> - - - + +
+ state.guistate.themeIsOpened.setData(true)}> +
+ + + + +
+
+ state.guistate.menuIsOpened.setData(true)}> + + + Testmode - + + +
@@ -93,26 +103,19 @@
mapproperties.zoom.update(z => z+1)}> - + mapproperties.zoom.update(z => z-1)}> - + + construct={new GeolocationControl(state.geolocation, mapproperties).SetClass("block w-6 h-6 md:w-8 md:h-8")}>
-
- - - -
- v !== undefined)}> {selectedElement.setData(undefined)}}> @@ -166,7 +169,7 @@ state={state.overlayLayerStates.get(tilesource.id)} highlightedLayer={state.guistate.highlightedLayerInFilters} zoomlevel={state.mapProperties.zoom} - /> + /> {/each} diff --git a/assets/layers/recycling/recycling.json b/assets/layers/recycling/recycling.json index 1aa0629439..e0b67625ed 100644 --- a/assets/layers/recycling/recycling.json +++ b/assets/layers/recycling/recycling.json @@ -23,7 +23,7 @@ "calculatedTags": [ "_waste_amount=Object.values(Object.keys(feat.properties).filter((key) => key.startsWith('recycling:')).reduce((cur, key) => { return Object.assign(cur, { [key]: feat.properties[key] })}, {})).reduce((n, x) => n + (x == \"yes\"), 0);" ], - "minzoom": 12, + "minzoom": 11, "title": { "render": { "en": "Recycling facility", @@ -36,12 +36,7 @@ { "if": "name~*", "then": { - "*": "{name}", - "en": "Recycling centre", - "nl": "Recyclingcentrum", - "de": "Wertstoffhof", - "es": "Centro de reciclaje", - "it": "Centro di riciclo rifiuti" + "*": "{name}" } }, { @@ -1410,4 +1405,4 @@ "enableRelocation": true, "enableImproveAccuracy": true } -} \ No newline at end of file +} diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 8d01e38753..ccfaecf549 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -787,14 +787,14 @@ video { margin: 0.5rem; } -.m-4 { - margin: 1rem; -} - .m-1 { margin: 0.25rem; } +.m-4 { + margin: 1rem; +} + .m-6 { margin: 1.5rem; } @@ -803,16 +803,16 @@ video { margin: 1px; } -.my-2 { - margin-top: 0.5rem; - margin-bottom: 0.5rem; -} - .mx-1 { margin-left: 0.25rem; margin-right: 0.25rem; } +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + .my-4 { margin-top: 1rem; margin-bottom: 1rem; @@ -833,10 +833,6 @@ video { margin-bottom: 0.75rem; } -.mb-4 { - margin-bottom: 1rem; -} - .mr-2 { margin-right: 0.5rem; } @@ -857,18 +853,30 @@ video { margin-top: 1.5rem; } -.mt-2 { - margin-top: 0.5rem; +.mr-0\.5 { + margin-right: 0.125rem; } -.ml-2 { - margin-left: 0.5rem; +.mr-0 { + margin-right: 0px; +} + +.mr-1 { + margin-right: 0.25rem; +} + +.mb-4 { + margin-bottom: 1rem; } .ml-4 { margin-left: 1rem; } +.mt-2 { + margin-top: 0.5rem; +} + .mb-24 { margin-bottom: 6rem; } @@ -877,6 +885,10 @@ video { margin-left: 0.25rem; } +.ml-2 { + margin-left: 0.5rem; +} + .mb-2 { margin-bottom: 0.5rem; } @@ -889,10 +901,6 @@ video { margin-bottom: 2.5rem; } -.mr-1 { - margin-right: 0.25rem; -} - .mt-0 { margin-top: 0px; } @@ -917,10 +925,6 @@ video { margin-bottom: 0.25rem; } -.mr-0 { - margin-right: 0px; -} - .-ml-6 { margin-left: -1.5rem; } @@ -1025,6 +1029,12 @@ video { height: 0.75rem; } +.h-fit { + height: -webkit-fit-content; + height: -moz-fit-content; + height: fit-content; +} + .h-11 { height: 2.75rem; } @@ -1331,26 +1341,26 @@ video { word-break: break-all; } -.rounded-xl { - border-radius: 0.75rem; -} - -.rounded-lg { - border-radius: 0.5rem; +.rounded { + border-radius: 0.25rem; } .rounded-3xl { border-radius: 1.5rem; } -.rounded { - border-radius: 0.25rem; -} - .rounded-full { border-radius: 9999px; } +.rounded-xl { + border-radius: 0.75rem; +} + +.rounded-lg { + border-radius: 0.5rem; +} + .rounded-md { border-radius: 0.375rem; } @@ -1505,10 +1515,6 @@ video { padding: 0.5rem; } -.p-3 { - padding: 0.75rem; -} - .p-0\.5 { padding: 0.125rem; } @@ -1517,6 +1523,10 @@ video { padding: 0px; } +.p-3 { + padding: 0.75rem; +} + .p-8 { padding: 2rem; } @@ -2282,16 +2292,35 @@ input { color: var(--unsubtle-detail-color-contrast); } +@media (max-width: 320px) { + .max-\[320px\]\:w-full { + width: 100%; + } +} + @media (min-width: 640px) { + .sm\:m-2 { + margin: 0.5rem; + } + .sm\:m-6 { margin: 1.5rem; } + .sm\:mx-1 { + margin-left: 0.25rem; + margin-right: 0.25rem; + } + .sm\:mx-auto { margin-left: auto; margin-right: auto; } + .sm\:mr-1 { + margin-right: 0.25rem; + } + .sm\:mt-5 { margin-top: 1.25rem; } @@ -2308,10 +2337,23 @@ input { height: 6rem; } + .sm\:h-6 { + height: 1.5rem; + } + .sm\:w-24 { width: 6rem; } + .sm\:w-min { + width: -webkit-min-content; + width: min-content; + } + + .sm\:w-6 { + width: 1.5rem; + } + .sm\:max-w-xl { max-width: 36rem; } @@ -2336,6 +2378,10 @@ input { padding: 1.5rem; } + .sm\:p-1 { + padding: 0.25rem; + } + .sm\:p-2 { padding: 0.5rem; } @@ -2364,6 +2410,10 @@ input { margin: 0.25rem; } + .md\:m-4 { + margin: 1rem; + } + .md\:m-8 { margin: 2rem; } @@ -2372,14 +2422,27 @@ input { margin: 0.5rem; } - .md\:mt-5 { - margin-top: 1.25rem; + .md\:mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; + } + + .md\:mr-2 { + margin-right: 0.5rem; + } + + .md\:mr-4 { + margin-right: 1rem; } .md\:mt-4 { margin-top: 1rem; } + .md\:mt-5 { + margin-top: 1.25rem; + } + .md\:flex { display: flex; } @@ -2388,10 +2451,18 @@ input { display: grid; } + .md\:h-8 { + height: 2rem; + } + .md\:h-12 { height: 3rem; } + .md\:w-8 { + width: 2rem; + } + .md\:w-2\/6 { width: 33.333333%; } From 84f83595283d73fc7cf8746509318e7a8654d465 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 24 Apr 2023 02:34:39 +0200 Subject: [PATCH 067/257] Styling: theme view buttons --- UI/ThemeViewGUI.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index b34655ce6e..fdcab69100 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -71,12 +71,12 @@
-
+
-
+
state.guistate.themeIsOpened.setData(true)}>
From 7d941e8a9a083d67ca0afdf99b98effe898435cf Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 24 Apr 2023 02:43:18 +0200 Subject: [PATCH 068/257] Styling: main map view buttons --- UI/ThemeViewGUI.svelte | 2 +- css/index-tailwind-output.css | 45 ++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index fdcab69100..55a61fdc59 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -76,7 +76,7 @@ {selectedLayer}>
-
+
state.guistate.themeIsOpened.setData(true)}>
diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index ccfaecf549..5684c978ad 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -787,14 +787,14 @@ video { margin: 0.5rem; } -.m-1 { - margin: 0.25rem; -} - .m-4 { margin: 1rem; } +.m-1 { + margin: 0.25rem; +} + .m-6 { margin: 1.5rem; } @@ -853,6 +853,14 @@ video { margin-top: 1.5rem; } +.mt-1 { + margin-top: 0.25rem; +} + +.ml-1 { + margin-left: 0.25rem; +} + .mr-0\.5 { margin-right: 0.125rem; } @@ -873,22 +881,18 @@ video { margin-left: 1rem; } -.mt-2 { - margin-top: 0.5rem; -} - .mb-24 { margin-bottom: 6rem; } -.ml-1 { - margin-left: 0.25rem; -} - .ml-2 { margin-left: 0.5rem; } +.mt-2 { + margin-top: 0.5rem; +} + .mb-2 { margin-bottom: 0.5rem; } @@ -909,10 +913,6 @@ video { margin-top: 2rem; } -.mt-1 { - margin-top: 0.25rem; -} - .ml-3 { margin-left: 0.75rem; } @@ -1531,6 +1531,11 @@ video { padding: 2rem; } +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + .py-4 { padding-top: 1rem; padding-bottom: 1rem; @@ -2431,18 +2436,14 @@ input { margin-right: 0.5rem; } - .md\:mr-4 { - margin-right: 1rem; + .md\:mt-5 { + margin-top: 1.25rem; } .md\:mt-4 { margin-top: 1rem; } - .md\:mt-5 { - margin-top: 1.25rem; - } - .md\:flex { display: flex; } From 78c56f6fa2745fd57baae183420aa2fc36919df1 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 24 Apr 2023 02:54:15 +0200 Subject: [PATCH 069/257] Refactoring: save ID to the hash of the URL --- Logic/Actors/SelectedFeatureHandler.ts | 133 ------------------------- Models/ThemeConfig/LayoutConfig.ts | 2 +- Models/ThemeViewState.ts | 25 +++++ test/Logic/Actors/Actors.spec.ts | 30 +----- 4 files changed, 27 insertions(+), 163 deletions(-) delete mode 100644 Logic/Actors/SelectedFeatureHandler.ts diff --git a/Logic/Actors/SelectedFeatureHandler.ts b/Logic/Actors/SelectedFeatureHandler.ts deleted file mode 100644 index 3d30a9ca18..0000000000 --- a/Logic/Actors/SelectedFeatureHandler.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { UIEventSource } from "../UIEventSource" -import Loc from "../../Models/Loc" -import { ElementStorage } from "../ElementStorage" -import FeaturePipeline from "../FeatureSource/FeaturePipeline" -import { GeoOperations } from "../GeoOperations" -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import OsmObjectDownloader from "../Osm/OsmObjectDownloader" - -/** - * Makes sure the hash shows the selected element and vice-versa. - */ -export default class SelectedFeatureHandler { - private static readonly _no_trigger_on = new Set([ - "welcome", - "copyright", - "layers", - "new", - "filters", - "location_track", - "", - undefined, - ]) - private readonly hash: UIEventSource - private readonly state: { - selectedElement: UIEventSource - allElements: ElementStorage - locationControl: UIEventSource - layoutToUse: LayoutConfig - objectDownloader: OsmObjectDownloader - } - - constructor( - hash: UIEventSource, - state: { - selectedElement: UIEventSource - allElements: ElementStorage - featurePipeline: FeaturePipeline - locationControl: UIEventSource - layoutToUse: LayoutConfig - objectDownloader: OsmObjectDownloader - } - ) { - this.hash = hash - this.state = state - - // If the hash changes, set the selected element correctly - - const self = this - hash.addCallback(() => self.setSelectedElementFromHash()) - this.initialLoad() - } - - /** - * On startup: check if the hash is loaded and eventually zoom to it - * @private - */ - private initialLoad() { - const hash = this.hash.data - if (hash === undefined || hash === "" || hash.indexOf("-") >= 0) { - return - } - if (SelectedFeatureHandler._no_trigger_on.has(hash)) { - return - } - - if (!(hash.startsWith("node") || hash.startsWith("way") || hash.startsWith("relation"))) { - return - } - - this.state.objectDownloader.DownloadObjectAsync(hash).then((obj) => { - try { - if (obj === "deleted") { - return - } - console.log("Downloaded selected object from OSM-API for initial load: ", hash) - const geojson = obj.asGeoJson() - this.state.allElements.addOrGetElement(geojson) - this.state.selectedElement.setData(geojson) - this.zoomToSelectedFeature() - } catch (e) { - console.error(e) - } - }) - } - - private setSelectedElementFromHash() { - const state = this.state - const h = this.hash.data - if (h === undefined || h === "") { - // Hash has been cleared - we clear the selected element - state.selectedElement.setData(undefined) - } else { - // we search the element to select - const feature = state.allElements.ContainingFeatures.get(h) - if (feature === undefined) { - return - } - const currentlySelected = state.selectedElement.data - if (currentlySelected === undefined) { - state.selectedElement.setData(feature) - return - } - if (currentlySelected.properties?.id === feature.properties.id) { - // We already have the right feature - return - } - state.selectedElement.setData(feature) - } - } - - // If a feature is selected via the hash, zoom there - private zoomToSelectedFeature() { - const selected = this.state.selectedElement.data - if (selected === undefined) { - return - } - - const centerpoint = GeoOperations.centerpointCoordinates(selected) - const location = this.state.locationControl - location.data.lon = centerpoint[0] - location.data.lat = centerpoint[1] - - const minZoom = Math.max( - 14, - ...(this.state.layoutToUse?.layers?.map((l) => l.minzoomVisible) ?? []) - ) - if (location.data.zoom < minZoom) { - location.data.zoom = minZoom - } - - location.ping() - } -} diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts index c55eb95369..840ef60ee3 100644 --- a/Models/ThemeConfig/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -286,7 +286,7 @@ export default class LayoutConfig implements LayoutInformation { return { untranslated, total } } - public getMatchingLayer(tags: any): LayerConfig | undefined { + public getMatchingLayer(tags: Record): LayerConfig | undefined { if (tags === undefined) { return undefined } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index de712afd05..bad56a3bb9 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -48,6 +48,7 @@ import { Utils } from "../Utils" import { EliCategory } from "./RasterLayerProperties" import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter" import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" +import Hash from "../Logic/Web/Hash" /** * @@ -429,6 +430,30 @@ export default class ThemeViewState implements SpecialVisualizationState { * Setup various services for which no reference are needed */ private initActors() { + this.selectedElement.addCallback((selected) => { + Hash.hash.setData(selected?.properties?.id) + }) + + Hash.hash.mapD( + (hash) => { + console.log("Searching for an id:", hash) + if (this.selectedElement.data?.properties?.id === hash) { + // We already have the correct hash + return + } + + const found = this.indexedFeatures.featuresById.data?.get(hash) + console.log("Found:", found) + if (!found) { + return + } + const layer = this.layout.getMatchingLayer(found.properties) + this.selectedElement.setData(found) + this.selectedLayer.setData(layer) + }, + [this.indexedFeatures.featuresById] + ) + new MetaTagging(this) new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) new ChangeToElementsActor(this.changes, this.featureProperties) diff --git a/test/Logic/Actors/Actors.spec.ts b/test/Logic/Actors/Actors.spec.ts index a1a4354267..419dd0be17 100644 --- a/test/Logic/Actors/Actors.spec.ts +++ b/test/Logic/Actors/Actors.spec.ts @@ -1,16 +1,12 @@ import { Utils } from "../../../Utils" -import UserRelatedState from "../../../Logic/State/UserRelatedState" import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig" import SelectedElementTagsUpdater from "../../../Logic/Actors/SelectedElementTagsUpdater" import * as bookcaseJson from "../../../assets/generated/themes/bookcases.json" -import { UIEventSource } from "../../../Logic/UIEventSource" -import Loc from "../../../Models/Loc" -import SelectedFeatureHandler from "../../../Logic/Actors/SelectedFeatureHandler" import { OsmTags } from "../../../Models/OsmFeature" import { Feature, Geometry } from "geojson" import { expect, it } from "vitest" -import ThemeViewState from "../../../Models/ThemeViewState"; +import ThemeViewState from "../../../Models/ThemeViewState" const latestTags = { amenity: "public_bookcase", @@ -86,27 +82,3 @@ it("should download the latest version", () => { // The fixme should be removed expect(feature.properties.fixme).toBeUndefined() }) -it("Hash without selected element should download geojson from OSM-API", async () => { - const hash = new UIEventSource("node/5568693115") - const selected = new UIEventSource(undefined) - const loc = new UIEventSource({ - lat: 0, - lon: 0, - zoom: 0, - }) - - loc.addCallback((_) => { - expect(selected.data.properties.id).toEqual("node/5568693115") - expect(loc.data.zoom).toEqual(14) - expect(loc.data.lat).toEqual(51.2179199) - } - - - new SelectedFeatureHandler(hash, { - selectedElement: selected, - allElements: new(), - featurePipeline: undefined, - locationControl: loc, - layoutToUse: undefined, - }) -}) From fcc49766d4eca1919759b8294c426e8c163c4374 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 24 Apr 2023 03:22:43 +0200 Subject: [PATCH 070/257] Refactoring: port statistics view --- Models/FilteredLayer.ts | 93 +++++++++++--------- UI/BigComponents/Filterview.svelte | 6 +- UI/BigComponents/FilterviewWithFields.svelte | 6 +- UI/BigComponents/TagRenderingChart.ts | 8 +- UI/StatisticsGUI.ts | 81 ++++++++--------- css/tabbedComponent.css | 74 ---------------- index.html | 2 - theme.html | 2 - 8 files changed, 103 insertions(+), 169 deletions(-) delete mode 100644 css/tabbedComponent.css diff --git a/Models/FilteredLayer.ts b/Models/FilteredLayer.ts index 9e4d83b310..de93485368 100644 --- a/Models/FilteredLayer.ts +++ b/Models/FilteredLayer.ts @@ -40,8 +40,17 @@ export default class FilteredLayer { ) { this.layerDef = layer this.isDisplayed = isDisplayed ?? new UIEventSource(true) - this.appliedFilters = - appliedFilters ?? new Map>() + if (!appliedFilters) { + const appliedFiltersWritable = new Map< + string, + UIEventSource + >() + for (const filter of this.layerDef.filters) { + appliedFiltersWritable.set(filter.id, new UIEventSource(undefined)) + } + appliedFilters = appliedFiltersWritable + } + this.appliedFilters = appliedFilters const self = this const currentTags = new UIEventSource(undefined) @@ -63,16 +72,6 @@ export default class FilteredLayer { return JSON.stringify(values) } - private static stringToFieldProperties(value: string): Record { - const values = JSON.parse(value) - for (const key in values) { - if (values[key] === "") { - delete values[key] - } - } - return values - } - /** * Creates a FilteredLayer which is tied into the QueryParameters and/or user preferences */ @@ -114,6 +113,16 @@ export default class FilteredLayer { return new FilteredLayer(layer, appliedFilters, isDisplayed) } + private static stringToFieldProperties(value: string): Record { + const values = JSON.parse(value) + for (const key in values) { + if (values[key] === "") { + delete values[key] + } + } + return values + } + private static fieldsToTags( option: FilterConfigOption, fieldstate: string | Record @@ -170,6 +179,36 @@ export default class FilteredLayer { this.appliedFilters.forEach((value) => value.setData(undefined)) } + /** + * Returns true if the given tags match the current filters (and the specified 'global filters') + */ + public isShown(properties: Record, globalFilters?: GlobalFilter[]): boolean { + if (properties._deleted === "yes") { + return false + } + { + const isShown: TagsFilter = this.layerDef.isShown + if (isShown !== undefined && !isShown.matchesProperties(properties)) { + return false + } + } + + { + let neededTags: TagsFilter = this.currentFilter.data + if (neededTags !== undefined && !neededTags.matchesProperties(properties)) { + return false + } + } + + for (const globalFilter of globalFilters ?? []) { + const neededTags = globalFilter.osmTags + if (neededTags !== undefined && !neededTags.matchesProperties(properties)) { + return false + } + } + return true + } + private calculateCurrentTags(): TagsFilter { let needed: TagsFilter[] = [] for (const filter of this.layerDef.filters) { @@ -209,34 +248,4 @@ export default class FilteredLayer { } return optimized } - - /** - * Returns true if the given tags match the current filters (and the specified 'global filters') - */ - public isShown(properties: Record, globalFilters?: GlobalFilter[]): boolean { - if (properties._deleted === "yes") { - return false - } - { - const isShown: TagsFilter = this.layerDef.isShown - if (isShown !== undefined && !isShown.matchesProperties(properties)) { - return false - } - } - - { - let neededTags: TagsFilter = this.currentFilter.data - if (neededTags !== undefined && !neededTags.matchesProperties(properties)) { - return false - } - } - - for (const globalFilter of globalFilters ?? []) { - const neededTags = globalFilter.osmTags - if (neededTags !== undefined && !neededTags.matchesProperties(properties)) { - return false - } - } - return true - } } diff --git a/UI/BigComponents/Filterview.svelte b/UI/BigComponents/Filterview.svelte index 172fea15d7..a7f7a86c02 100644 --- a/UI/BigComponents/Filterview.svelte +++ b/UI/BigComponents/Filterview.svelte @@ -10,14 +10,14 @@ import type { Writable } from "svelte/store"; import If from "../Base/If.svelte"; import Dropdown from "../Base/Dropdown.svelte"; import { onDestroy } from "svelte"; -import { UIEventSource } from "../../Logic/UIEventSource"; +import { ImmutableStore, Store } from "../../Logic/UIEventSource"; import FilterviewWithFields from "./FilterviewWithFields.svelte"; import Tr from "../Base/Tr.svelte"; import Translations from "../i18n/Translations"; export let filteredLayer: FilteredLayer; -export let highlightedLayer: UIEventSource | undefined; -export let zoomlevel: UIEventSource; +export let highlightedLayer: Store = new ImmutableStore(undefined); +export let zoomlevel: Store = new ImmutableStore(22); let layer: LayerConfig = filteredLayer.layerDef; let isDisplayed: boolean = filteredLayer.isDisplayed.data; onDestroy(filteredLayer.isDisplayed.addCallbackAndRunD(d => { diff --git a/UI/BigComponents/FilterviewWithFields.svelte b/UI/BigComponents/FilterviewWithFields.svelte index 3d37065033..8e356eb2f2 100644 --- a/UI/BigComponents/FilterviewWithFields.svelte +++ b/UI/BigComponents/FilterviewWithFields.svelte @@ -17,7 +17,7 @@ let fieldValues: Record> = {}; let fieldTypes: Record = {}; let appliedFilter = >filteredLayer.appliedFilters.get(id); - let initialState: Record = JSON.parse(appliedFilter.data ?? "{}"); + let initialState: Record = JSON.parse(appliedFilter?.data ?? "{}"); function setFields() { const properties: Record = {}; @@ -30,7 +30,7 @@ properties[k] = v; } } - appliedFilter.setData(FilteredLayer.fieldsToString(properties)); + appliedFilter?.setData(FilteredLayer.fieldsToString(properties)); } for (const field of option.fields) { @@ -38,7 +38,7 @@ fieldTypes[field.name + "}"] = field.type; const src = new UIEventSource(initialState[field.name] ?? ""); fieldValues[field.name + "}"] = src; - onDestroy(src.addCallback(() => { + onDestroy(src.stabilized(200).addCallback(() => { setFields(); })); } diff --git a/UI/BigComponents/TagRenderingChart.ts b/UI/BigComponents/TagRenderingChart.ts index 32a7371a17..c9b391c0b7 100644 --- a/UI/BigComponents/TagRenderingChart.ts +++ b/UI/BigComponents/TagRenderingChart.ts @@ -25,7 +25,13 @@ export class StackedRenderingChart extends ChartJs { groupToOtherCutoff: options?.groupToOtherCutoff, }) if (labels === undefined || data === undefined) { - console.error("Could not extract data and labels for ", tr, " with features", features) + console.error( + "Could not extract data and labels for ", + tr, + " with features", + features, + ": no labels or no data" + ) throw "No labels or data given..." } // labels: ["cyclofix", "buurtnatuur", ...]; data : [ ["cyclofix-changeset", "cyclofix-changeset", ...], ["buurtnatuur-cs", "buurtnatuur-cs"], ... ] diff --git a/UI/StatisticsGUI.ts b/UI/StatisticsGUI.ts index 4bd947ebd7..1e43a2a917 100644 --- a/UI/StatisticsGUI.ts +++ b/UI/StatisticsGUI.ts @@ -7,24 +7,25 @@ import Loading from "./Base/Loading" import { Utils } from "../Utils" import Combine from "./Base/Combine" import { StackedRenderingChart } from "./BigComponents/TagRenderingChart" -import { LayerFilterPanel } from "./BigComponents/FilterView" -import MapState from "../Logic/State/MapState" import BaseUIElement from "./BaseUIElement" import Title from "./Base/Title" import { FixedUiElement } from "./Base/FixedUiElement" import List from "./Base/List" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" import mcChanges from "../assets/generated/themes/mapcomplete-changes.json" +import SvelteUIElement from "./Base/SvelteUIElement" +import Filterview from "./BigComponents/Filterview.svelte" +import FilteredLayer from "../Models/FilteredLayer" + class StatisticsForOverviewFile extends Combine { constructor(homeUrl: string, paths: string[]) { paths = paths.filter((p) => !p.endsWith("file-overview.json")) const layer = new LayoutConfig(mcChanges, true).layers[0] - const filteredLayer = MapState.InitializeFilteredLayers( - { id: "statistics-view", layers: [layer] }, - undefined - )[0] - const filterPanel = new LayerFilterPanel(undefined, filteredLayer) - const appliedFilters = filteredLayer.appliedFilters + const filteredLayer = new FilteredLayer(layer) + const filterPanel = new Combine([ + new Title("Filters"), + new SvelteUIElement(Filterview, { filteredLayer }), + ]) const downloaded = new UIEventSource<{ features: ChangeSetData[] }[]>([]) @@ -63,20 +64,10 @@ class StatisticsForOverviewFile extends Combine { return loading } - let overview = ChangesetsOverview.fromDirtyData( + const overview = ChangesetsOverview.fromDirtyData( [].concat(...downloaded.map((d) => d.features)) - ) - if (appliedFilters.data.size > 0) { - appliedFilters.data.forEach((filterSpec) => { - const tf = filterSpec?.currentFilter - if (tf === undefined) { - return - } - overview = overview.filter((cs) => - tf.matchesProperties(cs.properties) - ) - }) - } + ).filter((cs) => filteredLayer.isShown(cs.properties)) + console.log("Overview is", overview) if (overview._meta.length === 0) { return "No data matched the filter" @@ -143,6 +134,10 @@ class StatisticsForOverviewFile extends Combine { new Title("Breakdown"), ] for (const tr of trs) { + if (tr.question === undefined) { + continue + } + console.log(tr) let total = undefined if (tr.freeform?.key !== undefined) { total = new Set( @@ -174,7 +169,7 @@ class StatisticsForOverviewFile extends Combine { return new Combine(elements) }, - [appliedFilters] + [filteredLayer.currentFilter] ) ).SetClass("block w-full h-full"), ]) @@ -232,30 +227,12 @@ class ChangesetsOverview { } public readonly _meta: ChangeSetData[] - public static fromDirtyData(meta: ChangeSetData[]) { - return new ChangesetsOverview(meta?.map((cs) => ChangesetsOverview.cleanChangesetData(cs))) - } - private constructor(meta: ChangeSetData[]) { this._meta = Utils.NoNull(meta) } - public filter(predicate: (cs: ChangeSetData) => boolean) { - return new ChangesetsOverview(this._meta.filter(predicate)) - } - - public sum(key: string, excludeThemes: Set): number { - let s = 0 - for (const feature of this._meta) { - if (excludeThemes.has(feature.properties.theme)) { - continue - } - const parsed = Number(feature.properties[key]) - if (!isNaN(parsed)) { - s += parsed - } - } - return s + public static fromDirtyData(meta: ChangeSetData[]) { + return new ChangesetsOverview(meta?.map((cs) => ChangesetsOverview.cleanChangesetData(cs))) } private static cleanChangesetData(cs: ChangeSetData): ChangeSetData { @@ -286,6 +263,24 @@ class ChangesetsOverview { } catch (e) {} return cs } + + public filter(predicate: (cs: ChangeSetData) => boolean) { + return new ChangesetsOverview(this._meta.filter(predicate)) + } + + public sum(key: string, excludeThemes: Set): number { + let s = 0 + for (const feature of this._meta) { + if (excludeThemes.has(feature.properties.theme)) { + continue + } + const parsed = Number(feature.properties[key]) + if (!isNaN(parsed)) { + s += parsed + } + } + return s + } } interface ChangeSetData { @@ -323,3 +318,5 @@ interface ChangeSetData { language: string } } + +new StatisticsGUI().AttachTo("main") diff --git a/css/tabbedComponent.css b/css/tabbedComponent.css deleted file mode 100644 index 39b949b29b..0000000000 --- a/css/tabbedComponent.css +++ /dev/null @@ -1,74 +0,0 @@ - -.tabs-header-bar { - padding-left: 1em; - padding-top: 10px; /* For the shadow */ - display: flex; - flex-direction: row; - flex-wrap: nowrap; - justify-content: flex-start; - align-items: start; - background-color: var(--background-color); - max-width: 100%; - overflow-x: auto; -} - - -.tab-single-header img { - height: 3em; - max-width: 3em; - padding: 0.5em; - display: block; - margin: auto; -} - -.tab-single-header svg { - height: 3em; - max-width: 3em; - padding: 0.5em; - display: block; - margin: auto; -} - - -.tab-single-header { - border-top-left-radius: 1em; - border-top-right-radius: 1em; - z-index: 5000; - padding-bottom: 0; - margin-bottom: 0; -} - -.tab-active { - background-color: var(--background-color); - color: var(--foreground-color); - z-index: 5001; - box-shadow: 0 0 10px var(--shadow-color); - border: 1px solid var(--background-color); - min-width: 4em; -} - -.tab-active svg { - fill: var(--foreground-color); - stroke: var(--foreground-color); -} - -.tab-non-active { - background-color: var(--subtle-detail-color); - color: var(--foreground-color); - opacity: 0.5; - border-left: 1px solid gray; - border-right: 1px solid gray; - border-top: 1px solid gray; - border-bottom: 1px solid lightgray; - min-width: 4em; -} - -.tab-non-active svg { - fill: var(--non-active-tab-svg) !important; - stroke: var(--non-active-tab-svg) !important; -} - -.tab-non-active svg path { - fill: var(--non-active-tab-svg) !important; - stroke: var(--non-active-tab-svg) !important; -} diff --git a/index.html b/index.html index 6cc4bd75c5..4175e853d9 100644 --- a/index.html +++ b/index.html @@ -3,8 +3,6 @@ - - diff --git a/theme.html b/theme.html index 1623a00c92..7dd7b6fb85 100644 --- a/theme.html +++ b/theme.html @@ -4,8 +4,6 @@ - - From 94635337e64c4c2fc78187dd1fb1015cd1d3d05b Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 24 Apr 2023 03:36:02 +0200 Subject: [PATCH 071/257] Refactoring: improve special components --- UI/InputElement/InputHelpers.ts | 2 +- UI/InputElement/ValidatedInput.svelte | 8 ++----- UI/InputElement/Validators/DateValidator.ts | 5 +++++ test.ts | 24 +++++++-------------- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/UI/InputElement/InputHelpers.ts b/UI/InputElement/InputHelpers.ts index 3090713904..46c1c9c071 100644 --- a/UI/InputElement/InputHelpers.ts +++ b/UI/InputElement/InputHelpers.ts @@ -76,7 +76,7 @@ export default class InputHelpers { mapProperties = { ...mapProperties, location } } let zoom = 17 - if (properties.args[0]) { + if (properties?.args?.[0] !== undefined) { zoom = Number(properties.args[0]) if (isNaN(zoom)) { throw "Invalid zoom level for argument at 'length'-input" diff --git a/UI/InputElement/ValidatedInput.svelte b/UI/InputElement/ValidatedInput.svelte index 761ef01e2d..697e0f7dac 100644 --- a/UI/InputElement/ValidatedInput.svelte +++ b/UI/InputElement/ValidatedInput.svelte @@ -10,7 +10,7 @@ export let value: UIEventSource; // Internal state, only copied to 'value' so that no invalid values leak outside let _value = new UIEventSource(value.data ?? ""); - onDestroy(value.addCallbackAndRun(v => _value.setData(v ?? ""))); + onDestroy(value.addCallbackAndRunD(v => _value.setData(v ?? ""))); export let type: ValidatorType; let validator = Validators.get(type); export let feedback: UIEventSource | undefined = undefined; @@ -34,12 +34,8 @@ let dispatch = createEventDispatcher<{ selected }>(); $: { - console.log(htmlElem); if (htmlElem !== undefined) { - htmlElem.onfocus = () => { - console.log("Dispatching selected event"); - return dispatch("selected"); - }; + htmlElem.onfocus = () => dispatch("selected"); } } diff --git a/UI/InputElement/Validators/DateValidator.ts b/UI/InputElement/Validators/DateValidator.ts index a67e371dd1..340b8159a8 100644 --- a/UI/InputElement/Validators/DateValidator.ts +++ b/UI/InputElement/Validators/DateValidator.ts @@ -10,6 +10,11 @@ export default class DateValidator extends Validator { } reformat(str: string) { + console.log("Reformatting", str) + if (!this.isValid(str)) { + // The date is invalid - we return the string as is + return str + } const d = new Date(str) let month = "" + (d.getMonth() + 1) let day = "" + d.getDate() diff --git a/test.ts b/test.ts index ddca5d1741..fd2294d46b 100644 --- a/test.ts +++ b/test.ts @@ -9,12 +9,8 @@ import { UIEventSource } from "./Logic/UIEventSource" import { VariableUiElement } from "./UI/Base/VariableUIElement" import { FixedUiElement } from "./UI/Base/FixedUiElement" import Title from "./UI/Base/Title" -import { WikipediaBoxOptions } from "./UI/Wikipedia/WikipediaBoxOptions" -import Wikipedia from "./Logic/Web/Wikipedia" -import WikipediaPanel from "./UI/Wikipedia/WikipediaPanel.svelte" import SvelteUIElement from "./UI/Base/SvelteUIElement" -import LanguagePicker from "./UI/LanguagePicker" -import { Utils } from "./Utils" +import ValidatedInput from "./UI/InputElement/ValidatedInput.svelte" function testspecial() { const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) @@ -37,10 +33,13 @@ function testinput() { }, }) + const feedback: UIEventSource = new UIEventSource(undefined) els.push( new Combine([ new Title(key), + new SvelteUIElement(ValidatedInput, { value, type: key, feedback }), helper, + new VariableUiElement(feedback), new VariableUiElement(value.map((v) => new FixedUiElement(v))), ]).SetClass("flex flex-col p-1 border-3 border-gray-500") ) @@ -48,14 +47,7 @@ function testinput() { new Combine(els).SetClass("flex flex-col").AttachTo("maindiv") } -async function testWaySplit() { - const ids = new UIEventSource(["Q42", "Q1"]) - new SvelteUIElement(WikipediaPanel, { wikiIds: ids, addEntry: true }).AttachTo("maindiv") - new LanguagePicker(["en", "nl"]).AttachTo("extradiv") - await Utils.waitFor(5000) - ids.data.push("Q430") - ids.ping() -} -testWaySplit().then((_) => console.log("inited")) -//testinput() -// testspecial() +testinput() +/*/ +testspecial() +//*/ From 5504d49d595fee66f0c43f3efbf6f130634497be Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 25 Apr 2023 02:37:23 +0200 Subject: [PATCH 072/257] Themes: enable download functionality by default --- Models/ThemeConfig/Json/LayoutConfigJson.ts | 4 ++-- Models/ThemeConfig/LayoutConfig.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Models/ThemeConfig/Json/LayoutConfigJson.ts b/Models/ThemeConfig/Json/LayoutConfigJson.ts index 9aa8c2e650..ad6f6a5175 100644 --- a/Models/ThemeConfig/Json/LayoutConfigJson.ts +++ b/Models/ThemeConfig/Json/LayoutConfigJson.ts @@ -276,11 +276,11 @@ export interface LayoutConfigJson { /** * If set to true, download button for the data will be shown (offers downloading as geojson and csv) */ - enableDownload?: false | boolean + enableDownload?: true | boolean /** * If set to true, exporting a pdf is enabled */ - enablePdfDownload?: false | boolean + enablePdfDownload?: true | boolean /** * If true, notes will be loaded and parsed. If a note is an import (as created by the import_helper.html-tool from mapcomplete), diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts index 840ef60ee3..be5cda3ec1 100644 --- a/Models/ThemeConfig/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -195,8 +195,8 @@ export default class LayoutConfig implements LayoutInformation { this.enableAddNewPoints = json.enableAddNewPoints ?? true this.enableBackgroundLayerSelection = json.enableBackgroundLayerSelection ?? true this.enableShowAllQuestions = json.enableShowAllQuestions ?? false - this.enableExportButton = json.enableDownload ?? false - this.enablePdfDownload = json.enablePdfDownload ?? false + this.enableExportButton = json.enableDownload ?? true + this.enablePdfDownload = json.enablePdfDownload ?? true this.customCss = json.customCss this.overpassUrl = Constants.defaultOverpassUrls if (json.overpassUrl !== undefined) { From 7fd7a3722e28ac79238b35e3794329a6b7d899d7 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 26 Apr 2023 18:04:42 +0200 Subject: [PATCH 073/257] Add level selector and global filters --- .../PerLayerFeatureSourceSplitter.ts | 41 +++-- .../FeatureSource/Sources/OsmFeatureSource.ts | 2 +- Logic/MetaTagging.ts | 14 +- Logic/State/LayerState.ts | 41 +++++ Models/FilteredLayer.ts | 12 +- Models/GlobalFilter.ts | 1 + Models/ThemeViewState.ts | 56 ++++++- UI/BigComponents/LevelSelector.svelte | 29 ++++ UI/BigComponents/LevelSelector.ts | 151 ------------------ UI/BigComponents/RightControls.ts | 11 -- UI/DefaultGUI.ts | 2 - UI/InputElement/Helpers/FloorSelector.svelte | 140 ++++++++++++++++ UI/Popup/AddNewPoint/AddNewPoint.svelte | 66 +++++--- UI/Popup/QuestionBox.ts | 16 -- UI/Popup/TagRendering/Questionbox.svelte | 3 + UI/ThemeViewGUI.svelte | 13 +- css/index-tailwind-output.css | 42 ++--- test.html | 2 +- test.ts | 12 +- 19 files changed, 401 insertions(+), 253 deletions(-) create mode 100644 UI/BigComponents/LevelSelector.svelte delete mode 100644 UI/BigComponents/LevelSelector.ts delete mode 100644 UI/BigComponents/RightControls.ts create mode 100644 UI/InputElement/Helpers/FloorSelector.svelte diff --git a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts index f437a90d69..f562e97481 100644 --- a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts +++ b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts @@ -1,8 +1,7 @@ -import { FeatureSource, FeatureSourceForLayer } from "./FeatureSource" +import { FeatureSource, IndexedFeatureSource } from "./FeatureSource" import FilteredLayer from "../../Models/FilteredLayer" import SimpleFeatureSource from "./Sources/SimpleFeatureSource" import { Feature } from "geojson" -import { Utils } from "../../Utils" import { UIEventSource } from "../UIEventSource" /** @@ -10,9 +9,7 @@ import { UIEventSource } from "../UIEventSource" * If this is the case, multiple objects with a different _matching_layer_id are generated. * In any case, this featureSource marks the objects with _matching_layer_id */ -export default class PerLayerFeatureSourceSplitter< - T extends FeatureSourceForLayer = SimpleFeatureSource -> { +export default class PerLayerFeatureSourceSplitter { public readonly perLayer: ReadonlyMap constructor( layers: FilteredLayer[], @@ -23,6 +20,11 @@ export default class PerLayerFeatureSourceSplitter< } ) { const knownLayers = new Map() + /** + * Keeps track of the ids that are included per layer. + * Used to know if the downstream feature source needs to be pinged + */ + let layerIndexes: ReadonlySet[] = layers.map((_) => new Set()) this.perLayer = knownLayers const layerSources = new Map>() const constructStore = @@ -41,6 +43,12 @@ export default class PerLayerFeatureSourceSplitter< // We try to figure out (for each feature) in which feature store it should be saved. const featuresPerLayer = new Map() + /** + * Indexed on layer-position + * Will be true if a new id pops up + */ + const hasChanged: boolean[] = layers.map((_) => false) + const newIndices: Set[] = layers.map((_) => new Set()) const noLayerFound: Feature[] = [] for (const layer of layers) { @@ -49,9 +57,14 @@ export default class PerLayerFeatureSourceSplitter< for (const f of features) { let foundALayer = false - for (const layer of layers) { + for (let i = 0; i < layers.length; i++) { + const layer = layers[i] if (layer.layerDef.source.osmTags.matchesProperties(f.properties)) { + const id = f.properties.id // We have found our matching layer! + const previousIndex = layerIndexes[i] + hasChanged[i] = hasChanged[i] || !previousIndex.has(id) + newIndices[i].add(id) featuresPerLayer.get(layer.layerDef.id).push(f) foundALayer = true if (!layer.layerDef.passAllFeatures) { @@ -67,7 +80,8 @@ export default class PerLayerFeatureSourceSplitter< // At this point, we have our features per layer as a list // We assign them to the correct featureSources - for (const layer of layers) { + for (let i = 0; i < layers.length; i++) { + const layer = layers[i] const id = layer.layerDef.id const features = featuresPerLayer.get(id) if (features === undefined) { @@ -75,14 +89,17 @@ export default class PerLayerFeatureSourceSplitter< continue } - const src = layerSources.get(id) - - if (Utils.sameList(src.data, features)) { + if (!hasChanged[i] && layerIndexes[i].size === newIndices[i].size) { + // No new id has been added and the sizes are the same (thus: nothing has been removed as well) + // We can safely assume that no changes were made continue } - src.setData(features) + + layerSources.get(id).setData(features) } + layerIndexes = newIndices + // AT last, the leftovers are handled if (options?.handleLeftovers !== undefined && noLayerFound.length > 0) { options.handleLeftovers(noLayerFound) @@ -90,7 +107,7 @@ export default class PerLayerFeatureSourceSplitter< }) } - public forEach(f: (featureSource: FeatureSourceForLayer) => void) { + public forEach(f: (featureSource: FeatureSource) => void) { for (const fs of this.perLayer.values()) { f(fs) } diff --git a/Logic/FeatureSource/Sources/OsmFeatureSource.ts b/Logic/FeatureSource/Sources/OsmFeatureSource.ts index 0c8949f7c1..968f03d0b0 100644 --- a/Logic/FeatureSource/Sources/OsmFeatureSource.ts +++ b/Logic/FeatureSource/Sources/OsmFeatureSource.ts @@ -122,7 +122,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger { throw "This is an absurd high zoom level" } - if (z < 14) { + if (z < 15) { throw `Zoom ${z} is too much for OSM to handle! Use a higher zoom level!` } const index = Tiles.tile_index(z, x, y) diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 15f10e4ab1..5aa42c4ec1 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -35,7 +35,18 @@ export default class MetaTagging { continue } const featureSource = state.perLayer.get(layer.id) - featureSource.features?.addCallbackAndRunD((features) => { + featureSource.features?.stabilized(1000)?.addCallbackAndRunD((features) => { + if (!(features?.length > 0)) { + // No features to handle + return + } + console.trace( + "Recalculating metatags for layer ", + layer.id, + "due to a change in the upstream features. Contains ", + features.length, + "items" + ) MetaTagging.addMetatags( features, params, @@ -71,7 +82,6 @@ export default class MetaTagging { return } - console.debug("Recalculating metatags...") const metatagsToApply: SimpleMetaTagger[] = [] for (const metatag of SimpleMetaTaggers.metatags) { if (metatag.includesDates) { diff --git a/Logic/State/LayerState.ts b/Logic/State/LayerState.ts index fc1f67135a..96f63dd19d 100644 --- a/Logic/State/LayerState.ts +++ b/Logic/State/LayerState.ts @@ -3,6 +3,8 @@ import { GlobalFilter } from "../../Models/GlobalFilter" import FilteredLayer from "../../Models/FilteredLayer" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { OsmConnection } from "../Osm/OsmConnection" +import { Tag } from "../Tags/Tag" +import Translations from "../../UI/i18n/Translations" /** * The layer state keeps track of: @@ -41,6 +43,45 @@ export default class LayerState { } this.filteredLayers = filteredLayers layers.forEach((l) => LayerState.linkFilterStates(l, filteredLayers)) + + this.globalFilters.data.push({ + id: "level", + osmTags: undefined, + state: undefined, + onNewPoint: undefined, + }) + } + + /** + * Sets the global filter which looks to the 'level'-tag. + * Only features with the given 'level' will be shown. + * + * If undefined is passed, _all_ levels will be shown + * @param level + */ + public setLevelFilter(level?: string) { + // Remove all previous + const l = this.globalFilters.data.length + this.globalFilters.data = this.globalFilters.data.filter((f) => f.id !== "level") + if (!level) { + if (l !== this.globalFilters.data.length) { + this.globalFilters.ping() + } + return + } + const t = Translations.t.general.levelSelection + this.globalFilters.data.push({ + id: "level", + state: level, + osmTags: new Tag("level", level), + onNewPoint: { + tags: [new Tag("level", level)], + icon: "./assets/svg/elevator.svg", + confirmAddNew: t.confirmLevel.PartialSubs({ level }), + safetyCheck: t.addNewOnLevel.Subs({ level }), + }, + }) + this.globalFilters.ping() } /** diff --git a/Models/FilteredLayer.ts b/Models/FilteredLayer.ts index de93485368..ee07c2d503 100644 --- a/Models/FilteredLayer.ts +++ b/Models/FilteredLayer.ts @@ -186,6 +186,12 @@ export default class FilteredLayer { if (properties._deleted === "yes") { return false } + for (const globalFilter of globalFilters ?? []) { + const neededTags = globalFilter.osmTags + if (neededTags !== undefined && !neededTags.matchesProperties(properties)) { + return false + } + } { const isShown: TagsFilter = this.layerDef.isShown if (isShown !== undefined && !isShown.matchesProperties(properties)) { @@ -200,12 +206,6 @@ export default class FilteredLayer { } } - for (const globalFilter of globalFilters ?? []) { - const neededTags = globalFilter.osmTags - if (neededTags !== undefined && !neededTags.matchesProperties(properties)) { - return false - } - } return true } diff --git a/Models/GlobalFilter.ts b/Models/GlobalFilter.ts index 91750c7bd7..ce837acca5 100644 --- a/Models/GlobalFilter.ts +++ b/Models/GlobalFilter.ts @@ -8,6 +8,7 @@ export interface GlobalFilter { id: string onNewPoint: { safetyCheck: Translation + icon: string confirmAddNew: TypedTranslation<{ preset: Translation }> tags: Tag[] } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index bad56a3bb9..659683d2c9 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -92,6 +92,10 @@ export default class ThemeViewState implements SpecialVisualizationState { string, { readonly isDisplayed: UIEventSource } > + /** + * All 'level'-tags that are available with the current features + */ + readonly floors: Store constructor(layout: LayoutConfig) { this.layout = layout @@ -214,17 +218,29 @@ export default class ThemeViewState implements SpecialVisualizationState { this.featureProperties ) + const doShowLayer = this.mapProperties.zoom.map( + (z) => + (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), + [fs.layer.isDisplayed] + ) + + if ( + !doShowLayer.data && + (this.featureSwitches.featureSwitchFilter.data === false || !fs.layer.layerDef.name) + ) { + /* This layer is hidden and there is no way to enable it (filterview is disabled or this layer doesn't show up in the filter view as the name is not defined) + * + * This means that we don't have to filter it, nor do we have to display it + * */ + return + } + const filtered = new FilteringFeatureSource( fs.layer, fs, (id) => this.featureProperties.getStore(id), this.layerState.globalFilters ) - const doShowLayer = this.mapProperties.zoom.map( - (z) => - (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), - [fs.layer.isDisplayed] - ) new ShowDataLayer(this.map, { layer: fs.layer.layerDef, @@ -236,6 +252,33 @@ export default class ThemeViewState implements SpecialVisualizationState { }) }) + this.floors = this.indexedFeatures.features.stabilized(500).map((features) => { + if (!features) { + return [] + } + const floors = new Set() + for (const feature of features) { + const level = feature.properties["level"] + if (level) { + floors.add(level) + } + } + const sorted = Array.from(floors) + // Sort alphabetically first, to deal with floor "A", "B" and "C" + sorted.sort() + sorted.sort((a, b) => { + // We use the laxer 'parseInt' to deal with floor '1A' + const na = parseInt(a) + const nb = parseInt(b) + if (isNaN(na) || isNaN(nb)) { + return 0 + } + return na - nb + }) + sorted.reverse(/* new list, no side-effects */) + return sorted + }) + const lastClick = (this.lastClickObject = new LastClickFeatureSource( this.mapProperties.lastClickLocation, this.layout @@ -443,7 +486,6 @@ export default class ThemeViewState implements SpecialVisualizationState { } const found = this.indexedFeatures.featuresById.data?.get(hash) - console.log("Found:", found) if (!found) { return } @@ -451,7 +493,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.selectedElement.setData(found) this.selectedLayer.setData(layer) }, - [this.indexedFeatures.featuresById] + [this.indexedFeatures.featuresById.stabilized(250)] ) new MetaTagging(this) diff --git a/UI/BigComponents/LevelSelector.svelte b/UI/BigComponents/LevelSelector.svelte new file mode 100644 index 0000000000..68b29a71b4 --- /dev/null +++ b/UI/BigComponents/LevelSelector.svelte @@ -0,0 +1,29 @@ + +{#if $zoom >= maxZoom} + + {/if} diff --git a/UI/BigComponents/LevelSelector.ts b/UI/BigComponents/LevelSelector.ts deleted file mode 100644 index d40020f259..0000000000 --- a/UI/BigComponents/LevelSelector.ts +++ /dev/null @@ -1,151 +0,0 @@ -import FloorLevelInputElement from "../Input/FloorLevelInputElement" -import MapState from "../../Logic/State/MapState" -import { TagsFilter } from "../../Logic/Tags/TagsFilter" -import { RegexTag } from "../../Logic/Tags/RegexTag" -import { Or } from "../../Logic/Tags/Or" -import { Tag } from "../../Logic/Tags/Tag" -import Translations from "../i18n/Translations" -import Combine from "../Base/Combine" -import { OsmFeature } from "../../Models/OsmFeature" -import { BBox } from "../../Logic/BBox" -import { TagUtils } from "../../Logic/Tags/TagUtils" -import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" -import { Store } from "../../Logic/UIEventSource" -import { GlobalFilter } from "../../Logic/State/GlobalFilter" - -/*** - * The element responsible for the level input element and picking the right level, showing and hiding at the right time, ... - */ -export default class LevelSelector extends Combine { - constructor(state: MapState & { featurePipeline: FeaturePipeline }) { - const levelsInView: Store> = state.currentBounds.map((bbox) => { - if (bbox === undefined) { - return {} - } - const allElementsUnfiltered: OsmFeature[] = [].concat( - ...state.featurePipeline.GetAllFeaturesAndMetaWithin(bbox).map((ff) => ff.features) - ) - const allElements = allElementsUnfiltered.filter((f) => BBox.get(f).overlapsWith(bbox)) - const allLevelsRaw: string[] = allElements.map((f) => f.properties["level"]) - - const levels: Record = { "0": 0 } - for (const levelDescription of allLevelsRaw) { - if (levelDescription === undefined) { - levels["0"]++ - } - for (const level of TagUtils.LevelsParser(levelDescription)) { - levels[level] = (levels[level] ?? 0) + 1 - } - } - - return levels - }) - - const levelSelect = new FloorLevelInputElement(levelsInView) - - state.globalFilters.data.push({ - filter: { - currentFilter: undefined, - state: undefined, - }, - id: "level", - onNewPoint: undefined, - }) - const isShown = levelsInView.map( - (levelsInView) => { - if (state.locationControl.data.zoom <= 16) { - return false - } - if (Object.keys(levelsInView).length == 1) { - return false - } - - return true - }, - [state.locationControl] - ) - - function setLevelFilter() { - console.log( - "Updating levels filter to ", - levelSelect.GetValue().data, - " is shown:", - isShown.data - ) - const filter: GlobalFilter = state.globalFilters.data.find((gf) => gf.id === "level") - if (!isShown.data) { - filter.filter = { - state: "*", - currentFilter: undefined, - } - filter.onNewPoint = undefined - state.globalFilters.ping() - return - } - - const l = levelSelect.GetValue().data - if (l === undefined) { - return - } - - let neededLevel: TagsFilter = new RegexTag("level", new RegExp("(^|;)" + l + "(;|$)")) - if (l === "0") { - neededLevel = new Or([neededLevel, new Tag("level", "")]) - } - filter.filter = { - state: l, - currentFilter: neededLevel, - } - const t = Translations.t.general.levelSelection - filter.onNewPoint = { - confirmAddNew: t.confirmLevel.PartialSubs({ level: l }), - safetyCheck: t.addNewOnLevel.Subs({ level: l }), - tags: [new Tag("level", l)], - } - state.globalFilters.ping() - return - } - - isShown.addCallbackAndRun((shown) => { - console.log("Is level selector shown?", shown) - setLevelFilter() - if (shown) { - levelSelect.RemoveClass("invisible") - } else { - levelSelect.SetClass("invisible") - } - }) - - levelsInView.addCallbackAndRun((levels) => { - if (!isShown.data) { - return - } - const value = levelSelect.GetValue() - if (!(levels[value.data] === undefined || levels[value.data] === 0)) { - return - } - // Nothing in view. Lets switch to a different level (the level with the most features) - let mostElements = 0 - let mostElementsLevel = undefined - for (const level in levels) { - const count = levels[level] - if (mostElementsLevel === undefined || mostElements < count) { - mostElementsLevel = level - mostElements = count - } - } - console.log( - "Force switching to a different level:", - mostElementsLevel, - "as it has", - mostElements, - "elements on that floor", - levels, - "(old level: " + value.data + ")" - ) - value.setData(mostElementsLevel) - }) - levelSelect.GetValue().addCallback((_) => setLevelFilter()) - super([levelSelect]) - } -} diff --git a/UI/BigComponents/RightControls.ts b/UI/BigComponents/RightControls.ts deleted file mode 100644 index 7c5280abac..0000000000 --- a/UI/BigComponents/RightControls.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Combine from "../Base/Combine" -import MapState from "../../Logic/State/MapState" -import LevelSelector from "./LevelSelector" - -export default class RightControls extends Combine { - constructor(state: MapState & { featurePipeline: FeaturePipeline }) { - const levelSelector = new LevelSelector(state) - super([levelSelector].map((el) => el.SetClass("m-0.5 md:m-1"))) - this.SetClass("flex flex-col items-center") - } -} diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index c5d08a8e7e..24d9436808 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -1,6 +1,5 @@ import Toggle from "./Input/Toggle" import LeftControls from "./BigComponents/LeftControls" -import RightControls from "./BigComponents/RightControls" import CenterMessageBox from "./CenterMessageBox" import { DefaultGuiState } from "./DefaultGuiState" import Combine from "./Base/Combine" @@ -42,7 +41,6 @@ export default class DefaultGUI { const guiState = this.guiState new LeftControls(state, guiState).AttachTo("bottom-left") - new RightControls(state, this.geolocationHandler).AttachTo("bottom-right") new CenterMessageBox(state).AttachTo("centermessage") document?.getElementById("centermessage")?.classList?.add("pointer-events-none") diff --git a/UI/InputElement/Helpers/FloorSelector.svelte b/UI/InputElement/Helpers/FloorSelector.svelte new file mode 100644 index 0000000000..faa65eee25 --- /dev/null +++ b/UI/InputElement/Helpers/FloorSelector.svelte @@ -0,0 +1,140 @@ + + +
+
+ {#each $floors as floor, i} + + {/each} +
+ +
+ +
+
+ + + + diff --git a/UI/Popup/AddNewPoint/AddNewPoint.svelte b/UI/Popup/AddNewPoint/AddNewPoint.svelte index 1a31ba7a44..7904e6196c 100644 --- a/UI/Popup/AddNewPoint/AddNewPoint.svelte +++ b/UI/Popup/AddNewPoint/AddNewPoint.svelte @@ -25,22 +25,36 @@ import { Tag } from "../../../Logic/Tags/Tag"; import type { WayId } from "../../../Models/OsmFeature"; import Loading from "../../Base/Loading.svelte"; + import type { GlobalFilter } from "../../../Models/GlobalFilter"; + import { onDestroy } from "svelte"; export let coordinate: { lon: number, lat: number }; export let state: SpecialVisualizationState; - let selectedPreset: { preset: PresetConfig, layer: LayerConfig, icon: string, tags: Record } = undefined; - + let selectedPreset: { + preset: PresetConfig, + layer: LayerConfig, + icon: string, + tags: Record + } = undefined; + let checkedOfGlobalFilters : number = 0 let confirmedCategory = false; $: if (selectedPreset === undefined) { confirmedCategory = false; creating = false; + checkedOfGlobalFilters = 0 + } let flayer: FilteredLayer = undefined; let layerIsDisplayed: UIEventSource | undefined = undefined; let layerHasFilters: Store | undefined = undefined; - + let globalFilter: UIEventSource = state.layerState.globalFilters; + let _globalFilter: GlobalFilter[]; + onDestroy(globalFilter.addCallbackAndRun(globalFilter => { + console.log("Global filters are", globalFilter); + _globalFilter = globalFilter ?? []; + })); $:{ flayer = state.layerState.filteredLayers.get(selectedPreset?.layer?.id); layerIsDisplayed = flayer?.isDisplayed; @@ -71,38 +85,38 @@ creating = true; const location: { lon: number; lat: number } = preciseCoordinate.data; const snapTo: WayId | undefined = snappedToObject.data; - const tags: Tag[] = selectedPreset.preset.tags; + const tags: Tag[] = selectedPreset.preset.tags.concat(..._globalFilter.map(f => f.onNewPoint.tags)); console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags); - let snapToWay: undefined | OsmWay = undefined - if(snapTo !== undefined){ + let snapToWay: undefined | OsmWay = undefined; + if (snapTo !== undefined) { const downloaded = await state.osmObjectDownloader.DownloadObjectAsync(snapTo, 0); - if(downloaded !== "deleted"){ - snapToWay = downloaded + if (downloaded !== "deleted") { + snapToWay = downloaded; } } const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, { - theme: state.layout?.id ?? "unkown", - changeType: "create", - snapOnto: snapToWay - }); - await state.changes.applyAction(newElementAction) - state.newFeatures.features.ping() + theme: state.layout?.id ?? "unkown", + changeType: "create", + snapOnto: snapToWay + }); + await state.changes.applyAction(newElementAction); + state.newFeatures.features.ping(); // The 'changes' should have created a new point, which added this into the 'featureProperties' const newId = newElementAction.newElementId; - console.log("Applied pending changes, fetching store for", newId) + console.log("Applied pending changes, fetching store for", newId); const tagsStore = state.featureProperties.getStore(newId); { // Set some metainfo const properties = tagsStore.data; if (snapTo) { // metatags (starting with underscore) are not uploaded, so we can safely mark this - delete properties["_referencing_ways"] + delete properties["_referencing_ways"]; properties["_referencing_ways"] = `["${snapTo}"]`; } - properties["_backend"] = state.osmConnection.Backend() + properties["_backend"] = state.osmConnection.Backend(); properties["_last_edit:timestamp"] = new Date().toISOString(); const userdetails = state.osmConnection.userDetails.data; properties["_last_edit:contributor"] = userdetails.name; @@ -113,13 +127,17 @@ abort(); state.selectedLayer.setData(selectedPreset.layer); state.selectedElement.setData(feature); - tagsStore.ping() + tagsStore.ping(); } + @@ -163,7 +181,7 @@ {:else if $layerHasFilters} - +
@@ -231,6 +249,16 @@
+ {:else if _globalFilter.length > checkedOfGlobalFilters} + + {checkedOfGlobalFilters = checkedOfGlobalFilters + 1}}> + + + + {globalFilter.setData([]); abort()}}> + + + {:else if !creating} - public readonly restingQuestions: Store - constructor( state, options: { @@ -29,10 +26,6 @@ export default class QuestionBox extends VariableUiElement { const tagsSource = options.tagsSource const units = options.units - options.showAllQuestionsAtOnce = options.showAllQuestionsAtOnce ?? false - const tagRenderings = options.tagRenderings - .filter((tr) => tr.question !== undefined) - .filter((tr) => tr.question !== null) let focus: () => void = () => {} @@ -59,9 +52,6 @@ export default class QuestionBox extends VariableUiElement { ) ) - const skippedQuestionsButton = Translations.t.general.skippedQuestions.onClick(() => { - skippedQuestions.setData([]) - }) tagsSource.map( (tags) => { if (tags === undefined) { @@ -136,18 +126,12 @@ export default class QuestionBox extends VariableUiElement { els.push(allQuestions[0]) } - if (skippedQuestions.data.length > 0) { - els.push(skippedQuestionsButton) - } - return new Combine(els).SetClass("block mb-8") }, [state.osmConnection.apiIsOnline] ) ) - this.skippedQuestions = skippedQuestions - this.restingQuestions = questionsToAsk focus = () => this.ScrollIntoView() } } diff --git a/UI/Popup/TagRendering/Questionbox.svelte b/UI/Popup/TagRendering/Questionbox.svelte index b095c5df51..c8573ca847 100644 --- a/UI/Popup/TagRendering/Questionbox.svelte +++ b/UI/Popup/TagRendering/Questionbox.svelte @@ -72,6 +72,9 @@ let answered: number = 0; let skipped: number = 0; + function focus(){ + + } function skip(question: TagRenderingConfig, didAnswer: boolean = false) { skippedQuestions.data.add(question.id); skippedQuestions.ping(); diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 55a61fdc59..d032edcbcd 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -5,7 +5,6 @@ import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; import MapControlButton from "./Base/MapControlButton.svelte"; import ToSvelte from "./Base/ToSvelte.svelte"; - import Svg from "../Svg"; import If from "./Base/If.svelte"; import { GeolocationControl } from "./BigComponents/GeolocationControl"; import type { Feature } from "geojson"; @@ -35,6 +34,7 @@ import { VariableUiElement } from "./Base/VariableUIElement"; import SvelteUIElement from "./Base/SvelteUIElement"; import OverlayToggle from "./BigComponents/OverlayToggle.svelte"; + import LevelSelector from "./BigComponents/LevelSelector.svelte"; export let state: ThemeViewState; let layout = state.layout; @@ -71,14 +71,14 @@
-
+
state.guistate.themeIsOpened.setData(true)}> -
+
@@ -101,7 +101,12 @@
-
+
+ f.length > 1)}> +
+ +
+
mapproperties.zoom.update(z => z+1)}> diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 5684c978ad..4ce129e4f0 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -787,14 +787,14 @@ video { margin: 0.5rem; } -.m-4 { - margin: 1rem; -} - .m-1 { margin: 0.25rem; } +.m-4 { + margin: 1rem; +} + .m-6 { margin: 1.5rem; } @@ -857,10 +857,6 @@ video { margin-top: 0.25rem; } -.ml-1 { - margin-left: 0.25rem; -} - .mr-0\.5 { margin-right: 0.125rem; } @@ -885,6 +881,10 @@ video { margin-bottom: 6rem; } +.ml-1 { + margin-left: 0.25rem; +} + .ml-2 { margin-left: 0.5rem; } @@ -1146,6 +1146,11 @@ video { width: 12rem; } +.w-min { + width: -webkit-min-content; + width: min-content; +} + .w-auto { width: auto; } @@ -1260,6 +1265,10 @@ video { align-content: flex-start; } +.items-end { + align-items: flex-end; +} + .items-center { align-items: center; } @@ -2297,8 +2306,8 @@ input { color: var(--unsubtle-detail-color-contrast); } -@media (max-width: 320px) { - .max-\[320px\]\:w-full { +@media (max-width: 480px) { + .max-\[480px\]\:w-full { width: 100%; } } @@ -2322,6 +2331,10 @@ input { margin-right: auto; } + .sm\:mt-2 { + margin-top: 0.5rem; + } + .sm\:mr-1 { margin-right: 0.25rem; } @@ -2350,11 +2363,6 @@ input { width: 6rem; } - .sm\:w-min { - width: -webkit-min-content; - width: min-content; - } - .sm\:w-6 { width: 1.5rem; } @@ -2415,10 +2423,6 @@ input { margin: 0.25rem; } - .md\:m-4 { - margin: 1rem; - } - .md\:m-8 { margin: 2rem; } diff --git a/test.html b/test.html index 2a5d2478c9..aece232743 100644 --- a/test.html +++ b/test.html @@ -17,7 +17,7 @@ -
'maindiv' not attached
+
'maindiv' not attached
'extradiv' not attached
diff --git a/test.ts b/test.ts index fd2294d46b..f8863551ab 100644 --- a/test.ts +++ b/test.ts @@ -5,12 +5,13 @@ import Combine from "./UI/Base/Combine" import SpecialVisualizations from "./UI/SpecialVisualizations" import InputHelpers from "./UI/InputElement/InputHelpers" import BaseUIElement from "./UI/BaseUIElement" -import { UIEventSource } from "./Logic/UIEventSource" +import { ImmutableStore, UIEventSource } from "./Logic/UIEventSource" import { VariableUiElement } from "./UI/Base/VariableUIElement" import { FixedUiElement } from "./UI/Base/FixedUiElement" import Title from "./UI/Base/Title" import SvelteUIElement from "./UI/Base/SvelteUIElement" import ValidatedInput from "./UI/InputElement/ValidatedInput.svelte" +import LevelSelector from "./UI/InputElement/Helpers/LevelSelector.svelte" function testspecial() { const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) @@ -47,7 +48,14 @@ function testinput() { new Combine(els).SetClass("flex flex-col").AttachTo("maindiv") } -testinput() +function testElevator() { + const floors = new ImmutableStore(["0", "1", "1.5", "2"]) + const value = new UIEventSource(undefined) + new SvelteUIElement(LevelSelector, { floors, value }).AttachTo("maindiv") + new VariableUiElement(value).AttachTo("extradiv") +} +testElevator() +//testinput() /*/ testspecial() //*/ From 06631ccd6d4604bf4386f5c816b3dcb5371273d2 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 27 Apr 2023 00:58:21 +0200 Subject: [PATCH 074/257] Refactoring: stabilize touchingFeatureSource --- .../Sources/TouchesBboxFeatureSource.ts | 30 +++++++++++++------ Models/ThemeViewState.ts | 5 +++- UI/BigComponents/LevelSelector.svelte | 2 +- UI/SpecialVisualizations.ts | 2 +- UI/ThemeViewGUI.svelte | 10 +++---- 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts b/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts index 3a6c6ce49e..31613fee04 100644 --- a/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts +++ b/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts @@ -1,28 +1,40 @@ -import {FeatureSource, FeatureSourceForLayer } from "../FeatureSource" +import { FeatureSource, FeatureSourceForLayer } from "../FeatureSource" import StaticFeatureSource from "./StaticFeatureSource" -import { GeoOperations } from "../../GeoOperations" import { BBox } from "../../BBox" import FilteredLayer from "../../../Models/FilteredLayer" +import { Store } from "../../UIEventSource" /** * Results in a feature source which has all the elements that touch the given features */ export default class BBoxFeatureSource extends StaticFeatureSource { - constructor(features: FeatureSource, mustTouch: BBox) { - const bbox = mustTouch.asGeoJson({}) + constructor(features: FeatureSource, mustTouch: Store) { super( - features.features.mapD((features) => - features.filter((feature) => GeoOperations.intersect(feature, bbox) !== undefined) + features.features.mapD( + (features) => { + if (mustTouch.data === undefined) { + return features + } + console.log("UPdating touching bbox") + const box = mustTouch.data + return features.filter((feature) => { + if (feature.geometry.type === "Point") { + return box.contains(<[number, number]>feature.geometry.coordinates) + } + return box.overlapsWith(BBox.get(feature)) + }) + }, + [mustTouch] ) ) } } export class BBoxFeatureSourceForLayer extends BBoxFeatureSource implements FeatureSourceForLayer { - constructor(features: FeatureSourceForLayer, mustTouch: BBox) { + readonly layer: FilteredLayer + + constructor(features: FeatureSourceForLayer, mustTouch: Store) { super(features, mustTouch) this.layer = features.layer } - - readonly layer: FilteredLayer } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 659683d2c9..95d81d4bd1 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -49,6 +49,7 @@ import { EliCategory } from "./RasterLayerProperties" import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter" import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" import Hash from "../Logic/Web/Hash" +import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" /** * @@ -79,6 +80,7 @@ export default class ThemeViewState implements SpecialVisualizationState { readonly historicalUserLocations: WritableFeatureSource> readonly indexedFeatures: IndexedFeatureSource & LayoutSource + readonly featuresInView: FeatureSource readonly newFeatures: WritableFeatureSource readonly layerState: LayerState readonly perLayer: ReadonlyMap @@ -167,6 +169,7 @@ export default class ThemeViewState implements SpecialVisualizationState { (id) => self.layerState.filteredLayers.get(id).isDisplayed ) this.indexedFeatures = layoutSource + this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) this.dataIsLoading = layoutSource.isLoading const indexedElements = this.indexedFeatures @@ -252,7 +255,7 @@ export default class ThemeViewState implements SpecialVisualizationState { }) }) - this.floors = this.indexedFeatures.features.stabilized(500).map((features) => { + this.floors = this.featuresInView.features.stabilized(500).map((features) => { if (!features) { return [] } diff --git a/UI/BigComponents/LevelSelector.svelte b/UI/BigComponents/LevelSelector.svelte index 68b29a71b4..9734cd9a01 100644 --- a/UI/BigComponents/LevelSelector.svelte +++ b/UI/BigComponents/LevelSelector.svelte @@ -22,7 +22,7 @@ layerState.setLevelFilter(floor) } }, [floors, zoom]) - + {#if $zoom >= maxZoom} diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index a9d62655b0..e7daccd832 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -1188,7 +1188,7 @@ export default class SpecialVisualizations { .map( (l) => { const fs = state.perLayer.get(l.id) - const bbox = state.mapProperties.bounds.data + const bbox = state.mapProperties.bounds const fsBboxed = new BBoxFeatureSourceForLayer(fs, bbox) return new StatisticsPanel(fsBboxed) }, diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index d032edcbcd..5c6b33e133 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -79,14 +79,14 @@
state.guistate.themeIsOpened.setData(true)}>
- +
state.guistate.menuIsOpened.setData(true)}> - + @@ -108,15 +108,15 @@
mapproperties.zoom.update(z => z+1)}> - + mapproperties.zoom.update(z => z-1)}> - + + construct={new GeolocationControl(state.geolocation, mapproperties).SetClass("block w-8 h-8")}>
From 2b47cf934caa2e1be9a5e0b17bdbc7585c1608e3 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 27 Apr 2023 02:24:38 +0200 Subject: [PATCH 075/257] Refactoring: highlight the currently selected element --- .../PerLayerFeatureSourceSplitter.ts | 2 +- .../Conversion/LegacyJsonConvert.ts | 2 +- Models/ThemeConfig/PointRenderingConfig.ts | 2 +- Models/ThemeViewState.ts | 54 ++++++++++++------- UI/Map/ShowDataLayer.ts | 49 +++++++++++++---- assets/layers/defibrillator/aed_checked.svg | 8 --- .../layers/defibrillator/defibrillator.json | 6 +-- assets/layers/defibrillator/defibrillator.svg | 45 ++++++++++++++++ assets/layers/defibrillator/license_info.json | 2 +- .../public_bookcase/public_bookcase.json | 4 +- assets/layers/toilet/toilet.json | 4 +- assets/layers/toilet/toilets.svg | 3 +- assets/svg/circle.svg | 11 ++-- assets/svg/pin.svg | 1 + assets/svg/square.svg | 25 +++++++-- assets/svg/star.svg | 4 +- assets/svg/teardrop.svg | 4 +- assets/themes/bookcases/bookcase.svg | 3 +- css/index-tailwind-output.css | 33 +++++++++--- index.css | 24 +++++++++ 20 files changed, 214 insertions(+), 72 deletions(-) delete mode 100644 assets/layers/defibrillator/aed_checked.svg create mode 100644 assets/layers/defibrillator/defibrillator.svg diff --git a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts index f562e97481..38b64748b4 100644 --- a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts +++ b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts @@ -107,7 +107,7 @@ export default class PerLayerFeatureSourceSplitter void) { + public forEach(f: (featureSource: T) => void) { for (const fs of this.perLayer.values()) { f(fs) } diff --git a/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts b/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts index 3b5ac7f8ea..f0f9b805ce 100644 --- a/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts +++ b/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts @@ -30,7 +30,7 @@ export class UpdateLegacyLayer extends DesugaringStep< config.source = config.source ?? { osmTags: config["overpassTags"], } - config.source.osmTags = config["overpassTags"] + config.source["osmTags"] = config["overpassTags"] delete config["overpassTags"] } diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts index 0c51b05394..250f92e95c 100644 --- a/Models/ThemeConfig/PointRenderingConfig.ts +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -13,7 +13,7 @@ import Combine from "../../UI/Base/Combine" import { VariableUiElement } from "../../UI/Base/VariableUIElement" export default class PointRenderingConfig extends WithContextLoader { - private static readonly allowed_location_codes = new Set([ + static readonly allowed_location_codes: ReadonlySet = new Set([ "point", "centroid", "start", diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 95d81d4bd1..19af47bcc1 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -476,29 +476,45 @@ export default class ThemeViewState implements SpecialVisualizationState { * Setup various services for which no reference are needed */ private initActors() { - this.selectedElement.addCallback((selected) => { - Hash.hash.setData(selected?.properties?.id) - }) + { + // Set the hash based on the selected element... + this.selectedElement.addCallback((selected) => { + Hash.hash.setData(selected?.properties?.id) + }) + // ... search and select an element based on the hash + Hash.hash.mapD( + (hash) => { + console.log("Searching for an id:", hash) + if (this.selectedElement.data?.properties?.id === hash) { + // We already have the correct hash + return + } - Hash.hash.mapD( - (hash) => { - console.log("Searching for an id:", hash) - if (this.selectedElement.data?.properties?.id === hash) { - // We already have the correct hash + const found = this.indexedFeatures.featuresById.data?.get(hash) + if (!found) { + return + } + const layer = this.layout.getMatchingLayer(found.properties) + this.selectedElement.setData(found) + this.selectedLayer.setData(layer) + }, + [this.indexedFeatures.featuresById.stabilized(250)] + ) + } + + { + // Unselect the selected element if it is panned out of view + this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => { + const selected = this.selectedElement.data + if (selected === undefined) { return } - - const found = this.indexedFeatures.featuresById.data?.get(hash) - if (!found) { - return + const bbox = BBox.get(selected) + if (!bbox.overlapsWith(bounds)) { + this.selectedElement.setData(undefined) } - const layer = this.layout.getMatchingLayer(found.properties) - this.selectedElement.setData(found) - this.selectedLayer.setData(layer) - }, - [this.indexedFeatures.featuresById.stabilized(250)] - ) - + }) + } new MetaTagging(this) new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) new ChangeToElementsActor(this.changes, this.featureProperties) diff --git a/UI/Map/ShowDataLayer.ts b/UI/Map/ShowDataLayer.ts index 7c1189ab1b..de2bacd78f 100644 --- a/UI/Map/ShowDataLayer.ts +++ b/UI/Map/ShowDataLayer.ts @@ -6,7 +6,7 @@ import { GeoOperations } from "../../Logic/GeoOperations" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import PointRenderingConfig from "../../Models/ThemeConfig/PointRenderingConfig" import { OsmTags } from "../../Models/OsmFeature" -import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource" +import { FeatureSource, FeatureSourceForLayer } from "../../Logic/FeatureSource/FeatureSource" import { BBox } from "../../Logic/BBox" import { Feature, Point } from "geojson" import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" @@ -15,7 +15,7 @@ import * as range_layer from "../../assets/layers/range/range.json" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter" import FilteredLayer from "../../Models/FilteredLayer" -import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" +import SimpleFeatureSource from "../../Logic/FeatureSource/Sources/SimpleFeatureSource" class PointRenderingLayer { private readonly _config: PointRenderingConfig @@ -24,6 +24,8 @@ class PointRenderingLayer { private readonly _map: MlMap private readonly _onClick: (feature: Feature) => void private readonly _allMarkers: Map = new Map() + private readonly _selectedElement: Store<{ properties: { id?: string } }> + private readonly _markedAsSelected: HTMLElement[] = [] private _dirty = false constructor( @@ -32,13 +34,15 @@ class PointRenderingLayer { config: PointRenderingConfig, visibility?: Store, fetchStore?: (id: string) => Store>, - onClick?: (feature: Feature) => void + onClick?: (feature: Feature) => void, + selectedElement?: Store<{ properties: { id?: string } }> ) { this._visibility = visibility this._config = config this._map = map this._fetchStore = fetchStore this._onClick = onClick + this._selectedElement = selectedElement const self = this features.features.addCallbackAndRunD((features) => self.updateFeatures(features)) @@ -48,6 +52,24 @@ class PointRenderingLayer { } self.setVisibility(visible) }) + selectedElement?.addCallbackAndRun((selected) => { + this._markedAsSelected.forEach((el) => el.classList.remove("selected")) + this._markedAsSelected.splice(0, this._markedAsSelected.length) + if (selected === undefined) { + return + } + PointRenderingConfig.allowed_location_codes.forEach((code) => { + const marker = this._allMarkers + .get(selected.properties?.id + "-" + code) + ?.getElement() + if (marker === undefined) { + return + } + console.log("Marking", marker, "as selected for config", config) + marker?.classList?.add("selected") + this._markedAsSelected.push(marker) + }) + }) } private updateFeatures(features: Feature[]) { @@ -91,6 +113,10 @@ class PointRenderingLayer { } const marker = this.addPoint(feature, loc) + if (this._selectedElement?.data === feature.properties.id) { + marker.getElement().classList.add("selected") + this._markedAsSelected.push(marker.getElement()) + } cache.set(id, marker) } } @@ -179,6 +205,7 @@ class LineRenderingLayer { private readonly _onClick?: (feature: Feature) => void private readonly _layername: string private readonly _listenerInstalledOn: Set = new Set() + private currentSourceData constructor( map: MlMap, @@ -228,7 +255,6 @@ class LineRenderingLayer { return calculatedProps } - private currentSourceData private async update(featureSource: Store) { const map = this._map while (!map.isStyleLoaded()) { @@ -380,10 +406,14 @@ export default class ShowDataLayer { layers: LayerConfig[], options?: Partial ) { - const perLayer = new PerLayerFeatureSourceSplitter( - layers.filter((l) => l.source !== null).map((l) => new FilteredLayer(l)), - new StaticFeatureSource(features) - ) + const perLayer: PerLayerFeatureSourceSplitter = + new PerLayerFeatureSourceSplitter( + layers.filter((l) => l.source !== null).map((l) => new FilteredLayer(l)), + features, + { + constructStore: (features, layer) => new SimpleFeatureSource(layer, features), + } + ) perLayer.forEach((fs) => { new ShowDataLayer(mlmap, { layer: fs.layer.layerDef, @@ -445,7 +475,8 @@ export default class ShowDataLayer { pointRenderingConfig, doShowLayer, fetchStore, - onClick + onClick, + selectedElement ) } } diff --git a/assets/layers/defibrillator/aed_checked.svg b/assets/layers/defibrillator/aed_checked.svg deleted file mode 100644 index 246aab1e2e..0000000000 --- a/assets/layers/defibrillator/aed_checked.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/assets/layers/defibrillator/defibrillator.json b/assets/layers/defibrillator/defibrillator.json index 858e5755a6..2ab15b75de 100644 --- a/assets/layers/defibrillator/defibrillator.json +++ b/assets/layers/defibrillator/defibrillator.json @@ -650,11 +650,11 @@ "mapRendering": [ { "icon": { - "render": "./assets/themes/aed/aed.svg", + "render": "square:#008754;./assets/layers/defibrillator/defibrillator.svg", "mappings": [ { "if": "_recently_surveyed=true", - "then": "./assets/layers/defibrillator/aed_checked.svg" + "then": "square:#28ba3d;./assets/layers/defibrillator/defibrillator.svg" } ] }, @@ -670,4 +670,4 @@ "has_image", "open_now" ] -} \ No newline at end of file +} diff --git a/assets/layers/defibrillator/defibrillator.svg b/assets/layers/defibrillator/defibrillator.svg new file mode 100644 index 0000000000..cc3aca615d --- /dev/null +++ b/assets/layers/defibrillator/defibrillator.svg @@ -0,0 +1,45 @@ + + + + + + + + + diff --git a/assets/layers/defibrillator/license_info.json b/assets/layers/defibrillator/license_info.json index 353a61246e..28426cd49b 100644 --- a/assets/layers/defibrillator/license_info.json +++ b/assets/layers/defibrillator/license_info.json @@ -1,6 +1,6 @@ [ { - "path": "aed_checked.svg", + "path": "defibrillator.svg", "license": "CC0", "authors": [ "MaxxL" diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index cf839bd012..ad38f0d542 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -521,7 +521,7 @@ "mapRendering": [ { "icon": { - "render": "./assets/themes/bookcases/bookcase.svg" + "render": "circle:#ffffff;./assets/themes/bookcases/bookcase.svg" }, "label": { "mappings": [ @@ -545,4 +545,4 @@ } } ] -} \ No newline at end of file +} diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index 67041a2480..02ae4005c4 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -681,7 +681,7 @@ "mapRendering": [ { "icon": { - "render": "./assets/layers/toilet/toilets.svg", + "render": "circle:#ffffff;./assets/layers/toilet/toilets.svg", "mappings": [ { "if": { @@ -761,4 +761,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/assets/layers/toilet/toilets.svg b/assets/layers/toilet/toilets.svg index 02ade60d58..6edb85774c 100644 --- a/assets/layers/toilet/toilets.svg +++ b/assets/layers/toilet/toilets.svg @@ -1,7 +1,6 @@ - - \ No newline at end of file + diff --git a/assets/svg/circle.svg b/assets/svg/circle.svg index 2cfde629fa..fc6efd4fc7 100644 --- a/assets/svg/circle.svg +++ b/assets/svg/circle.svg @@ -1,6 +1,9 @@ - - - - \ No newline at end of file + + + + diff --git a/assets/svg/pin.svg b/assets/svg/pin.svg index 4a10b32da1..83f6ab2f6f 100644 --- a/assets/svg/pin.svg +++ b/assets/svg/pin.svg @@ -11,6 +11,7 @@ id="defs11" /> diff --git a/assets/svg/square.svg b/assets/svg/square.svg index 0bad670ff2..30a997c84e 100644 --- a/assets/svg/square.svg +++ b/assets/svg/square.svg @@ -1,6 +1,21 @@ - - - - + + + + - \ No newline at end of file + diff --git a/assets/svg/star.svg b/assets/svg/star.svg index d7148bcef4..61301ada96 100644 --- a/assets/svg/star.svg +++ b/assets/svg/star.svg @@ -1,6 +1,6 @@ - + - \ No newline at end of file + diff --git a/assets/svg/teardrop.svg b/assets/svg/teardrop.svg index efed7c4eb1..c3f5edf4f4 100644 --- a/assets/svg/teardrop.svg +++ b/assets/svg/teardrop.svg @@ -1,7 +1,7 @@ - + - \ No newline at end of file + diff --git a/assets/themes/bookcases/bookcase.svg b/assets/themes/bookcases/bookcase.svg index a49c40b27a..a7240f2ba5 100644 --- a/assets/themes/bookcases/bookcase.svg +++ b/assets/themes/bookcases/bookcase.svg @@ -1,7 +1,6 @@ - - \ No newline at end of file + diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 4ce129e4f0..3485f6eb7d 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -2094,6 +2094,31 @@ li::marker { display: none; } +.selected svg path.selectable { + stroke: white !important; + stroke-width: 20px !important; + /* filter: drop-shadow(5px 5px 40px rgb(0 0 0 / 0.6));*/ + overflow: visible !important; + -webkit-animation: glowing-drop-shadow 1s ease-in-out infinite alternate; + animation: glowing-drop-shadow 1s ease-in-out infinite alternate; +} + +.selected svg { + overflow: visible !important; +} + +@-webkit-keyframes glowing-drop-shadow { + from { + -webkit-filter: drop-shadow(5px 5px 60px rgb(128 128 128 / 0.6)); + filter: drop-shadow(5px 5px 60px rgb(128 128 128 / 0.6)); + } + + to { + -webkit-filter: drop-shadow(5px 5px 80px rgb(0.5 0.5 0.5 / 0.8)); + filter: drop-shadow(5px 5px 80px rgb(0.5 0.5 0.5 / 0.8)); + } +} + /**************** GENERIC ****************/ .alert { @@ -2355,18 +2380,10 @@ input { height: 6rem; } - .sm\:h-6 { - height: 1.5rem; - } - .sm\:w-24 { width: 6rem; } - .sm\:w-6 { - width: 1.5rem; - } - .sm\:max-w-xl { max-width: 36rem; } diff --git a/index.css b/index.css index a98dc2916c..d2378b8c74 100644 --- a/index.css +++ b/index.css @@ -304,6 +304,30 @@ li::marker { display: none; } +.selected svg path.selectable { + stroke: white !important; + stroke-width: 20px !important; + overflow: visible !important; + -webkit-animation: glowing-drop-shadow 1s ease-in-out infinite alternate; + -moz-animation: glowing-drop-shadow 1s ease-in-out infinite alternate; + animation: glowing-drop-shadow 1s ease-in-out infinite alternate; +} + +.selected svg { + overflow: visible !important; +} + + +@-webkit-keyframes glowing-drop-shadow { + from { + filter: drop-shadow(5px 5px 60px rgb(128 128 128 / 0.6)); + } + to { + filter: drop-shadow(5px 5px 80px rgb(0.5 0.5 0.5 / 0.8)); + } +} + + /**************** GENERIC ****************/ .alert { From 0c5205136b1a18ee91df65de52e9e60f2036d3b7 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 27 Apr 2023 02:29:59 +0200 Subject: [PATCH 076/257] Themes: fix rendering of charging station --- assets/layers/charging_station/charging_station.json | 4 ++-- assets/layers/charging_station/charging_station.protojson | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 56319e5ea8..f1f5ccd431 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -4620,7 +4620,7 @@ "id": "questions" }, { - "id": "questions", + "id": "questions-technical", "render": { "en": "

Technical questions

The questions below are very technical. Feel free to ignore them
{questions(technical)}", "nl": "

Technische vragen

De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt
{questions(technical)}" @@ -5218,4 +5218,4 @@ }, "neededChangesets": 10 } -} \ No newline at end of file +} diff --git a/assets/layers/charging_station/charging_station.protojson b/assets/layers/charging_station/charging_station.protojson index f092b4c091..d5ebc7ce18 100644 --- a/assets/layers/charging_station/charging_station.protojson +++ b/assets/layers/charging_station/charging_station.protojson @@ -721,7 +721,7 @@ "id": "questions" }, { - "id": "questions", + "id": "questions-technical", "render": { "en": "

Technical questions

The questions below are very technical. Feel free to ignore them
{questions(technical)}", "nl": "

Technische vragen

De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt
{questions(technical)}" From e001576d9ff46787bba47da3b82672a948c974c1 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 27 Apr 2023 10:04:03 +0200 Subject: [PATCH 077/257] Fix build --- test.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test.ts b/test.ts index f8863551ab..6f2e3c21b1 100644 --- a/test.ts +++ b/test.ts @@ -11,7 +11,6 @@ import { FixedUiElement } from "./UI/Base/FixedUiElement" import Title from "./UI/Base/Title" import SvelteUIElement from "./UI/Base/SvelteUIElement" import ValidatedInput from "./UI/InputElement/ValidatedInput.svelte" -import LevelSelector from "./UI/InputElement/Helpers/LevelSelector.svelte" function testspecial() { const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) @@ -48,13 +47,6 @@ function testinput() { new Combine(els).SetClass("flex flex-col").AttachTo("maindiv") } -function testElevator() { - const floors = new ImmutableStore(["0", "1", "1.5", "2"]) - const value = new UIEventSource(undefined) - new SvelteUIElement(LevelSelector, { floors, value }).AttachTo("maindiv") - new VariableUiElement(value).AttachTo("extradiv") -} -testElevator() //testinput() /*/ testspecial() From 518999c401d4b8c7a2fae4f78813f075c29e569c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 27 Apr 2023 16:32:36 +0200 Subject: [PATCH 078/257] Fix some errors --- Utils/svgToPdf.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Utils/svgToPdf.ts b/Utils/svgToPdf.ts index 524874432c..3f324af901 100644 --- a/Utils/svgToPdf.ts +++ b/Utils/svgToPdf.ts @@ -1,6 +1,5 @@ import jsPDF, { Matrix } from "jspdf" import { Translation, TypedTranslation } from "../UI/i18n/Translation" -import FeaturePipelineState from "../Logic/State/FeaturePipelineState" import { PngMapCreator } from "./pngMapCreator" import { AllKnownLayouts } from "../Customizations/AllKnownLayouts" import { Store } from "../Logic/UIEventSource" @@ -12,6 +11,7 @@ import Translations from "../UI/i18n/Translations" import { Utils } from "../Utils" import Constants from "../Models/Constants" import Hash from "../Logic/Web/Hash" +import ThemeViewState from "../Models/ThemeViewState" class SvgToPdfInternals { private readonly doc: jsPDF @@ -711,16 +711,16 @@ export class SvgToPdfPage { Hash.hash.setData(undefined) // QueryParameters.ClearAll() - const state = new FeaturePipelineState(layout) - state.locationControl.setData({ - zoom, + const state = new ThemeViewState(layout) + state.mapProperties.location.setData({ lat: this.options?.overrideLocation?.lat ?? Number(params["lat"] ?? 51.05016), lon: this.options?.overrideLocation?.lon ?? Number(params["lon"] ?? 3.717842), }) + state.mapProperties.zoom.setData(zoom) console.log("Params are", params, params["layers"] === "none") - const fl = state.filteredLayers.data + const fl = Array.from(state.layerState.filteredLayers.values()) for (const filteredLayer of fl) { if (params["layer-" + filteredLayer.layerDef.id] !== undefined) { filteredLayer.isDisplayed.setData( @@ -738,7 +738,7 @@ export class SvgToPdfPage { const layerName = paramsKey.substring("layer-".length) const key = params[paramsKey].toLowerCase().trim() const isDisplayed = key === "true" || key === "force" - const layer = state.filteredLayers.data.find((l) => l.layerDef.id === layerName) + const layer = fl.find((l) => l.layerDef.id === layerName) console.log( "Setting ", layer?.layerDef?.id, From 90275ee2f9ba5321c904f35088cb4ec83136d4a1 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 27 Apr 2023 16:33:27 +0200 Subject: [PATCH 079/257] Translation sync --- assets/layers/bike_shop/bike_shop.json | 2 +- assets/layers/charging_station/charging_station.json | 4 ++-- assets/layers/defibrillator/defibrillator.json | 2 +- .../maproulette_challenge/maproulette_challenge.json | 4 ++-- assets/layers/public_bookcase/public_bookcase.json | 2 +- assets/layers/recycling/recycling.json | 9 +++++++-- assets/layers/toilet/toilet.json | 2 +- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/assets/layers/bike_shop/bike_shop.json b/assets/layers/bike_shop/bike_shop.json index bb986b5595..522b875c21 100644 --- a/assets/layers/bike_shop/bike_shop.json +++ b/assets/layers/bike_shop/bike_shop.json @@ -840,4 +840,4 @@ "filter": [ "open_now" ] -} +} \ No newline at end of file diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index f1f5ccd431..56319e5ea8 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -4620,7 +4620,7 @@ "id": "questions" }, { - "id": "questions-technical", + "id": "questions", "render": { "en": "

Technical questions

The questions below are very technical. Feel free to ignore them
{questions(technical)}", "nl": "

Technische vragen

De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt
{questions(technical)}" @@ -5218,4 +5218,4 @@ }, "neededChangesets": 10 } -} +} \ No newline at end of file diff --git a/assets/layers/defibrillator/defibrillator.json b/assets/layers/defibrillator/defibrillator.json index 2ab15b75de..db6757c179 100644 --- a/assets/layers/defibrillator/defibrillator.json +++ b/assets/layers/defibrillator/defibrillator.json @@ -670,4 +670,4 @@ "has_image", "open_now" ] -} +} \ No newline at end of file diff --git a/assets/layers/maproulette_challenge/maproulette_challenge.json b/assets/layers/maproulette_challenge/maproulette_challenge.json index 3ab70fdd0b..327086c632 100644 --- a/assets/layers/maproulette_challenge/maproulette_challenge.json +++ b/assets/layers/maproulette_challenge/maproulette_challenge.json @@ -2,7 +2,7 @@ "id": "maproulette_challenge", "name": null, "description": { - "en": "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.", + "en": "Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to the documentation on how to do this.", "de": "Ebene mit Aufgaben einer einzelnen MapRoulette-Herausforderung. Diese Ebene soll in Themen wiederverwendet und erweitert werden; Informationen dazu finden Sie in der Dokumentation.", "nl": "Laag met taken van een MapRoulette uitdaging" }, @@ -224,4 +224,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index ad38f0d542..0b02777ccd 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -545,4 +545,4 @@ } } ] -} +} \ No newline at end of file diff --git a/assets/layers/recycling/recycling.json b/assets/layers/recycling/recycling.json index e0b67625ed..4d4a40339c 100644 --- a/assets/layers/recycling/recycling.json +++ b/assets/layers/recycling/recycling.json @@ -36,7 +36,12 @@ { "if": "name~*", "then": { - "*": "{name}" + "*": "{name}", + "de": "Wertstoffhof", + "en": "Recycling centre", + "es": "Centro de reciclaje", + "it": "Centro di riciclo rifiuti", + "nl": "Recyclingcentrum" } }, { @@ -1405,4 +1410,4 @@ "enableRelocation": true, "enableImproveAccuracy": true } -} +} \ No newline at end of file diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index 02ae4005c4..0b7846dbb7 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -761,4 +761,4 @@ ] } ] -} +} \ No newline at end of file From ba4fe62d2fc50340d751162ba55bd3e672d11e3b Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 27 Apr 2023 16:34:08 +0200 Subject: [PATCH 080/257] Chore: rename id in charging_station --- assets/layers/charging_station/charging_station.json | 4 ++-- langs/layers/en.json | 2 +- langs/layers/nl.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 56319e5ea8..f1f5ccd431 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -4620,7 +4620,7 @@ "id": "questions" }, { - "id": "questions", + "id": "questions-technical", "render": { "en": "

Technical questions

The questions below are very technical. Feel free to ignore them
{questions(technical)}", "nl": "

Technische vragen

De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt
{questions(technical)}" @@ -5218,4 +5218,4 @@ }, "neededChangesets": 10 } -} \ No newline at end of file +} diff --git a/langs/layers/en.json b/langs/layers/en.json index a226100c0c..787b2c8797 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -2530,7 +2530,7 @@ "question": "What power output does a single plug of type
Type 2 with cable (mennekes)
offer?", "render": "
Type 2 with cable (mennekes)
outputs at most {socket:type2_cable:output}" }, - "questions": { + "questions-technical": { "render": "

Technical questions

The questions below are very technical. Feel free to ignore them
{questions(technical)}" }, "ref": { diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 968ae958fc..8bac854c4c 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -2420,7 +2420,7 @@ "question": "Welk vermogen levert een enkele stekker van type
Type 2 met kabel (J1772)
?", "render": "
Type 2 met kabel (J1772)
levert een vermogen van maximaal {socket:type2_cable:output}" }, - "questions": { + "questions-technical": { "render": "

Technische vragen

De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt
{questions(technical)}" }, "ref": { From 07be3dbe268917f65841ceadfbc3032cd0956ffc Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 30 Apr 2023 12:50:50 +0200 Subject: [PATCH 081/257] show shops on lower zoom --- assets/layers/shops/shops.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json index e6253ad26d..a980ffbb00 100644 --- a/assets/layers/shops/shops.json +++ b/assets/layers/shops/shops.json @@ -12,7 +12,7 @@ "es": "Tienda", "pa_PK": "دکان" }, - "minzoom": 16, + "minzoom": 12, "source": { "osmTags": { "and": [ @@ -412,4 +412,4 @@ "accepts_cards", "has_organic" ] -} \ No newline at end of file +} From 6d9a984f328d7b5ae375597768741c0a6c65ac21 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 1 May 2023 01:11:05 +0200 Subject: [PATCH 082/257] Don't show a highlight of elements within the last-click animation --- assets/layers/last_click/last_click.json | 4 ++-- css/index-tailwind-output.css | 3 +-- index.css | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/assets/layers/last_click/last_click.json b/assets/layers/last_click/last_click.json index 5ff05941c4..c74fe83704 100644 --- a/assets/layers/last_click/last_click.json +++ b/assets/layers/last_click/last_click.json @@ -74,7 +74,7 @@ "then": "{first_preset}" } ], - "render": "
{renderings}{first_preset}
" + "render": "
{renderings}{first_preset}
" }, "labelCssClasses": "text-sm min-w-min pl-1 pr-1 rounded-3xl text-white opacity-65 whitespace-nowrap block-ruby", "labelCss": "background: #00000088", @@ -158,4 +158,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 3485f6eb7d..b1d4943490 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -2094,10 +2094,9 @@ li::marker { display: none; } -.selected svg path.selectable { +.selected svg:not(.noselect *) path.selectable { stroke: white !important; stroke-width: 20px !important; - /* filter: drop-shadow(5px 5px 40px rgb(0 0 0 / 0.6));*/ overflow: visible !important; -webkit-animation: glowing-drop-shadow 1s ease-in-out infinite alternate; animation: glowing-drop-shadow 1s ease-in-out infinite alternate; diff --git a/index.css b/index.css index d2378b8c74..61e14c301d 100644 --- a/index.css +++ b/index.css @@ -304,7 +304,7 @@ li::marker { display: none; } -.selected svg path.selectable { +.selected svg:not(.noselect *) path.selectable { stroke: white !important; stroke-width: 20px !important; overflow: visible !important; From 4c962eab21af8146d9d57f7d12cd08cdd4a642d6 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 1 May 2023 01:12:04 +0200 Subject: [PATCH 083/257] Fix: Correctly calculate available levels, reselect last_click automatically --- Models/ThemeViewState.ts | 88 ++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 19af47bcc1..b739ebb036 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -263,7 +263,10 @@ export default class ThemeViewState implements SpecialVisualizationState { for (const feature of features) { const level = feature.properties["level"] if (level) { - floors.add(level) + const levels = level.split(";") + for (const l of levels) { + floors.add(l) + } } } const sorted = Array.from(floors) @@ -378,42 +381,53 @@ export default class ThemeViewState implements SpecialVisualizationState { ) } + private addLastClick(last_click: LastClickFeatureSource) { + // The last_click gets a _very_ special treatment as it interacts with various parts + + const last_click_layer = this.layerState.filteredLayers.get("last_click") + this.featureProperties.trackFeatureSource(last_click) + this.indexedFeatures.addSource(last_click) + + last_click.features.addCallbackAndRunD((features) => { + if (this.selectedLayer.data?.id === "last_click") { + // The last-click location moved, but we have selected the last click of the previous location + // So, we update _after_ clearing the selection to make sure no stray data is sticking around + this.selectedElement.setData(undefined) + this.selectedElement.setData(features[0]) + } + }) + + new ShowDataLayer(this.map, { + features: new FilteringFeatureSource(last_click_layer, last_click), + doShowLayer: new ImmutableStore(true), + layer: last_click_layer.layerDef, + selectedElement: this.selectedElement, + selectedLayer: this.selectedLayer, + onClick: (feature: Feature) => { + if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) { + this.map.data.flyTo({ + zoom: Constants.minZoomLevelToAddNewPoint, + center: this.mapProperties.lastClickLocation.data, + }) + return + } + // We first clear the selection to make sure no weird state is around + this.selectedLayer.setData(undefined) + this.selectedElement.setData(undefined) + + this.selectedElement.setData(feature) + this.selectedLayer.setData(last_click_layer.layerDef) + }, + }) + } + /** * Add the special layers to the map */ private drawSpecialLayers(last_click: LastClickFeatureSource) { type AddedByDefaultTypes = typeof Constants.added_by_default[number] const empty = [] - { - // The last_click gets a _very_ special treatment - - const last_click_layer = this.layerState.filteredLayers.get("last_click") - this.featureProperties.trackFeatureSource(last_click) - this.indexedFeatures.addSource(last_click) - new ShowDataLayer(this.map, { - features: new FilteringFeatureSource(last_click_layer, last_click), - doShowLayer: new ImmutableStore(true), - layer: last_click_layer.layerDef, - selectedElement: this.selectedElement, - selectedLayer: this.selectedLayer, - onClick: (feature: Feature) => { - if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) { - this.map.data.flyTo({ - zoom: Constants.minZoomLevelToAddNewPoint, - center: this.mapProperties.lastClickLocation.data, - }) - return - } - // We first clear the selection to make sure no weird state is around - this.selectedLayer.setData(undefined) - this.selectedElement.setData(undefined) - - this.selectedElement.setData(feature) - this.selectedLayer.setData(last_click_layer.layerDef) - }, - }) - } - + this.addLastClick(last_click) /** * A listing which maps the layerId onto the featureSource */ @@ -494,7 +508,19 @@ export default class ThemeViewState implements SpecialVisualizationState { if (!found) { return } + if (found.properties.id === "last_click") { + return + } const layer = this.layout.getMatchingLayer(found.properties) + console.log( + "Setting selected element based on hash", + hash, + "; found", + found, + "got matching layer", + layer.id, + "" + ) this.selectedElement.setData(found) this.selectedLayer.setData(layer) }, From 7b73578f88b09ee65f923c363a884d91f20d2fae Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 1 May 2023 01:12:39 +0200 Subject: [PATCH 084/257] Fix: better select available levels --- Logic/State/LayerState.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Logic/State/LayerState.ts b/Logic/State/LayerState.ts index 96f63dd19d..ec5c65d3fd 100644 --- a/Logic/State/LayerState.ts +++ b/Logic/State/LayerState.ts @@ -5,6 +5,8 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { OsmConnection } from "../Osm/OsmConnection" import { Tag } from "../Tags/Tag" import Translations from "../../UI/i18n/Translations" +import { RegexTag } from "../Tags/RegexTag" +import { Or } from "../Tags/Or" /** * The layer state keeps track of: @@ -43,13 +45,6 @@ export default class LayerState { } this.filteredLayers = filteredLayers layers.forEach((l) => LayerState.linkFilterStates(l, filteredLayers)) - - this.globalFilters.data.push({ - id: "level", - osmTags: undefined, - state: undefined, - onNewPoint: undefined, - }) } /** @@ -73,7 +68,10 @@ export default class LayerState { this.globalFilters.data.push({ id: "level", state: level, - osmTags: new Tag("level", level), + osmTags: new Or([ + new Tag("level", "" + level), + new RegexTag("level", new RegExp("(.*;)?" + level + "(;.*)?")), + ]), onNewPoint: { tags: [new Tag("level", level)], icon: "./assets/svg/elevator.svg", From 78136532651ebcea6a2d2d94e42b2775bb9e92e3 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 1 May 2023 01:14:48 +0200 Subject: [PATCH 085/257] Fix: better error handling and handling of changes --- Logic/MetaTagging.ts | 6 ++++- UI/BigComponents/PdfExportGui.ts | 1 - UI/InputElement/Helpers/FloorSelector.svelte | 3 +++ UI/Popup/AddNewPoint/AddNewPoint.svelte | 6 ++--- UI/Popup/TagApplyButton.ts | 1 + .../TagRendering/SpecialTranslation.svelte | 23 ++++++++++++++++--- .../TagRendering/TagRenderingAnswer.svelte | 4 +++- UI/ThemeViewGUI.svelte | 2 +- 8 files changed, 36 insertions(+), 10 deletions(-) diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts index 5aa42c4ec1..99d7068d5d 100644 --- a/Logic/MetaTagging.ts +++ b/Logic/MetaTagging.ts @@ -164,7 +164,11 @@ export default class MetaTagging { } if (somethingChanged) { - featurePropertiesStores?.getStore(feature.properties.id)?.ping() + try { + featurePropertiesStores?.getStore(feature.properties.id)?.ping() + } catch (e) { + console.error("Could not ping a store for a changed property due to", e) + } atLeastOneFeatureChanged = true } } diff --git a/UI/BigComponents/PdfExportGui.ts b/UI/BigComponents/PdfExportGui.ts index 979de96589..dd280e692c 100644 --- a/UI/BigComponents/PdfExportGui.ts +++ b/UI/BigComponents/PdfExportGui.ts @@ -15,7 +15,6 @@ import BaseUIElement from "../BaseUIElement" import Img from "../Base/Img" import Title from "../Base/Title" import { CheckBox } from "../Input/Checkboxes" -import Minimap from "../Base/Minimap" import SearchAndGo from "./SearchAndGo" import Toggle from "../Input/Toggle" import List from "../Base/List" diff --git a/UI/InputElement/Helpers/FloorSelector.svelte b/UI/InputElement/Helpers/FloorSelector.svelte index faa65eee25..465ced6f9f 100644 --- a/UI/InputElement/Helpers/FloorSelector.svelte +++ b/UI/InputElement/Helpers/FloorSelector.svelte @@ -72,6 +72,9 @@ } Stores.Chronic(50).addCallback(_ => stabilize()); + floors.addCallback(floors => { + forceIndex = floors.findIndex(s => s === value.data) + }) let image: HTMLImageElement; $:{ diff --git a/UI/Popup/AddNewPoint/AddNewPoint.svelte b/UI/Popup/AddNewPoint/AddNewPoint.svelte index 7904e6196c..50028d4985 100644 --- a/UI/Popup/AddNewPoint/AddNewPoint.svelte +++ b/UI/Popup/AddNewPoint/AddNewPoint.svelte @@ -50,7 +50,7 @@ let layerIsDisplayed: UIEventSource | undefined = undefined; let layerHasFilters: Store | undefined = undefined; let globalFilter: UIEventSource = state.layerState.globalFilters; - let _globalFilter: GlobalFilter[]; + let _globalFilter: GlobalFilter[] = []; onDestroy(globalFilter.addCallbackAndRun(globalFilter => { console.log("Global filters are", globalFilter); _globalFilter = globalFilter ?? []; @@ -85,7 +85,7 @@ creating = true; const location: { lon: number; lat: number } = preciseCoordinate.data; const snapTo: WayId | undefined = snappedToObject.data; - const tags: Tag[] = selectedPreset.preset.tags.concat(..._globalFilter.map(f => f.onNewPoint.tags)); + const tags: Tag[] = selectedPreset.preset.tags.concat(..._globalFilter.map(f => f?.onNewPoint?.tags ?? [])); console.log("Creating new point at", location, "snapped to", snapTo, "with tags", tags); let snapToWay: undefined | OsmWay = undefined; @@ -249,7 +249,7 @@
- {:else if _globalFilter.length > checkedOfGlobalFilters} + {:else if _globalFilter?.length > 0 && _globalFilter?.length > checkedOfGlobalFilters} {checkedOfGlobalFilters = checkedOfGlobalFilters + 1}}> diff --git a/UI/Popup/TagApplyButton.ts b/UI/Popup/TagApplyButton.ts index 1027b224d1..dfe16d1b30 100644 --- a/UI/Popup/TagApplyButton.ts +++ b/UI/Popup/TagApplyButton.ts @@ -61,6 +61,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization if (kv.length == 2) { tgsSpec.push(<[string, string]>kv) } else if (kv.length < 2) { + console.error("Invalid key spec: no '=' found in " + spec) throw "Invalid key spec: no '=' found in " + spec } else { throw "Invalid key spec: multiple '=' found in " + spec diff --git a/UI/Popup/TagRendering/SpecialTranslation.svelte b/UI/Popup/TagRendering/SpecialTranslation.svelte index 3cea541166..377626e124 100644 --- a/UI/Popup/TagRendering/SpecialTranslation.svelte +++ b/UI/Popup/TagRendering/SpecialTranslation.svelte @@ -11,6 +11,7 @@ import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; import WeblateLink from "../../Base/WeblateLink.svelte"; import FromHtml from "../../Base/FromHtml.svelte"; + import BaseUIElement from "../../BaseUIElement"; /** * The 'specialTranslation' renders a `Translation`-object, but interprets the special values as well @@ -27,10 +28,26 @@ })); let specs: RenderingSpecification[] = []; $: { - if (txt !== undefined) { - specs = SpecialVisualizations.constructSpecification(txt); + try { + + if (txt !== undefined) { + specs = SpecialVisualizations.constructSpecification(txt); + } + } catch (e) { + console.error("Could not construct a specification and with arguments", txt); } } + + function createVisualisation(specpart: Exclude): BaseUIElement { + { + try { + return specpart.func.constr(state, tags, specpart.args, feature, layer); + } catch (e) { + console.error("Could not construct a special visualisation with specification", specpart, "and tags", tags); + } + } + } + {#each specs as specpart} @@ -40,6 +57,6 @@ {:else if $tags !== undefined } - + {/if} {/each} diff --git a/UI/Popup/TagRendering/TagRenderingAnswer.svelte b/UI/Popup/TagRendering/TagRenderingAnswer.svelte index 431814777a..e977dce32e 100644 --- a/UI/Popup/TagRendering/TagRenderingAnswer.svelte +++ b/UI/Popup/TagRendering/TagRenderingAnswer.svelte @@ -22,7 +22,9 @@ } export let layer: LayerConfig; let trs: { then: Translation; icon?: string; iconClass?: string }[]; - $: trs = Utils.NoNull(config?.GetRenderValues(_tags)); + $:{ + trs = Utils.NoNull(config?.GetRenderValues(_tags)); + } let classes = "" $:classes = config?.classes?.join(" ") ?? ""; diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 5c6b33e133..77c72ddab1 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -121,7 +121,7 @@
- v !== undefined)}> + v !== undefined && selectedLayer.data !== undefined,[ selectedLayer] )}> {selectedElement.setData(undefined)}}> From 1a78ecb85806db67f6e51e39a0882c038589a058 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 1 May 2023 01:16:17 +0200 Subject: [PATCH 086/257] Fix: only show 'newly created' if version number is 1 or unset --- assets/tagRenderings/questions.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index 65526d7d91..639947da54 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -1425,7 +1425,13 @@ "condition": { "and": [ "_backend~*", - "_last_edit:passed_time<300" + "_last_edit:passed_time<300", + { + "or": [ + "_version_number=", + "_version_number=1" + ] + } ] }, "metacondition": { @@ -2110,4 +2116,4 @@ } ] } -} \ No newline at end of file +} From 755f905c36f1e96d239bfee6a2a46415ca012052 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 1 May 2023 01:26:38 +0200 Subject: [PATCH 087/257] Bump version number --- Models/Constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Models/Constants.ts b/Models/Constants.ts index 18b6cf429c..3feecec19c 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -3,7 +3,7 @@ import { Utils } from "../Utils" export type PriviligedLayerType = typeof Constants.priviliged_layers[number] export default class Constants { - public static vNumber = "0.30.0" + public static vNumber = "0.30.1" public static ImgurApiKey = "7070e7167f0a25a" public static readonly mapillary_client_token_v4 = From 1f39ba9ab50f63e04be89f676aa82c04d5bb2f94 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 3 May 2023 00:57:15 +0200 Subject: [PATCH 088/257] Fix: fix validation of question input; remove some obsolete logging; stabilize output of generate:translations wrt newlines --- .../Sources/TouchesBboxFeatureSource.ts | 1 - Models/ThemeConfig/TagRenderingConfig.ts | 53 +- Models/ThemeViewState.ts | 6 +- UI/InputElement/ValidatedInput.svelte | 22 +- UI/InputElement/Validator.ts | 3 +- UI/InputElement/Validators/EmailValidator.ts | 5 +- UI/InputElement/Validators/PhoneValidator.ts | 30 +- UI/OpeningHours/OpeningHours.ts | 2 +- UI/OpeningHours/OpeningHoursVisualization.ts | 8 +- UI/Popup/MoveWizard.ts | 1 + UI/Popup/QuestionBox.ts | 2 + UI/Popup/TagRendering/FreeformInput.svelte | 7 +- .../TagRendering/TagRenderingQuestion.svelte | 24 +- UI/Popup/TagRenderingQuestion.ts | 1 + assets/fonts/Ubuntu-L-normal.js | 7 + assets/fonts/Ubuntu-M-normal.js | 7 + assets/fonts/UbuntuMono-B-bold.js | 7 + langs/en.json | 3 +- .../templates/MapComplete-flyer.back.svg | 908 +++++++ public/assets/templates/MapComplete-flyer.svg | 1492 ++++++++++ .../templates/MapComplete-poster-a2.svg | 2353 ++++++++++++++++ .../templates/MapComplete-poster-a3.svg | 2398 +++++++++++++++++ public/assets/templates/Ubuntu-L-normal.js | 7 + public/assets/templates/Ubuntu-M-normal.js | 7 + public/assets/templates/UbuntuMono-B-bold.js | 7 + scripts/generateTranslations.ts | 38 +- 26 files changed, 7328 insertions(+), 71 deletions(-) create mode 100644 assets/fonts/Ubuntu-L-normal.js create mode 100644 assets/fonts/Ubuntu-M-normal.js create mode 100644 assets/fonts/UbuntuMono-B-bold.js create mode 100644 public/assets/templates/MapComplete-flyer.back.svg create mode 100644 public/assets/templates/MapComplete-flyer.svg create mode 100644 public/assets/templates/MapComplete-poster-a2.svg create mode 100644 public/assets/templates/MapComplete-poster-a3.svg create mode 100644 public/assets/templates/Ubuntu-L-normal.js create mode 100644 public/assets/templates/Ubuntu-M-normal.js create mode 100644 public/assets/templates/UbuntuMono-B-bold.js diff --git a/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts b/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts index 31613fee04..ac16be8e86 100644 --- a/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts +++ b/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts @@ -15,7 +15,6 @@ export default class BBoxFeatureSource extends StaticFeatureSource { if (mustTouch.data === undefined) { return features } - console.log("UPdating touching bbox") const box = mustTouch.data return features.filter((feature) => { if (feature.geometry.type === "Point") { diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index ed486cad55..1f9bca6f53 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -1,22 +1,20 @@ -import { Translation, TypedTranslation } from "../../UI/i18n/Translation" -import { TagsFilter } from "../../Logic/Tags/TagsFilter" +import {Translation, TypedTranslation} from "../../UI/i18n/Translation" +import {TagsFilter} from "../../Logic/Tags/TagsFilter" import Translations from "../../UI/i18n/Translations" -import { TagUtils, UploadableTag } from "../../Logic/Tags/TagUtils" -import { And } from "../../Logic/Tags/And" -import { Utils } from "../../Utils" -import { Tag } from "../../Logic/Tags/Tag" +import {TagUtils, UploadableTag} from "../../Logic/Tags/TagUtils" +import {And} from "../../Logic/Tags/And" +import {Utils} from "../../Utils" +import {Tag} from "../../Logic/Tags/Tag" import BaseUIElement from "../../UI/BaseUIElement" import Combine from "../../UI/Base/Combine" import Title from "../../UI/Base/Title" import Link from "../../UI/Base/Link" import List from "../../UI/Base/List" -import { - MappingConfigJson, - QuestionableTagRenderingConfigJson, -} from "./Json/QuestionableTagRenderingConfigJson" -import { FixedUiElement } from "../../UI/Base/FixedUiElement" -import { Paragraph } from "../../UI/Base/Paragraph" +import {MappingConfigJson, QuestionableTagRenderingConfigJson,} from "./Json/QuestionableTagRenderingConfigJson" +import {FixedUiElement} from "../../UI/Base/FixedUiElement" +import {Paragraph} from "../../UI/Base/Paragraph" import Svg from "../../Svg" +import Validators, {ValidatorType} from "../../UI/InputElement/Validators"; export interface Mapping { readonly if: UploadableTag @@ -623,13 +621,19 @@ export default class TagRenderingConfig { * * @param singleSelectedMapping (Only used if multiAnswer == false): the single mapping to apply. Use (mappings.length) for the freeform * @param multiSelectedMapping (Only used if multiAnswer == true): all the mappings that must be applied. Set multiSelectedMapping[mappings.length] to use the freeform as well + * @param currentProperties: The current properties of the object for which the question should be answered */ public constructChangeSpecification( freeformValue: string | undefined, singleSelectedMapping: number, - multiSelectedMapping: boolean[] | undefined + multiSelectedMapping: boolean[] | undefined, + currentProperties: Record ): UploadableTag { freeformValue = freeformValue?.trim() + const validator = Validators.get( this.freeform?.type) + if(validator && freeformValue){ + freeformValue = validator.reformat(freeformValue,() => currentProperties["_country"]) + } if (freeformValue === "") { freeformValue = undefined } @@ -666,7 +670,7 @@ export default class TagRenderingConfig { .filter((_, i) => !multiSelectedMapping[i]) .map((m) => m.ifnot) - if (multiSelectedMapping.at(-1)) { + if (multiSelectedMapping.at(-1) && this.freeform) { // The freeform value was selected as well selectedMappings.push( new And([ @@ -677,22 +681,29 @@ export default class TagRenderingConfig { } return TagUtils.FlattenMultiAnswer([...selectedMappings, ...unselectedMappings]) } else { - if (singleSelectedMapping === undefined) { - return undefined - } - if (singleSelectedMapping === this.mappings.length) { - if (freeformValue === undefined) { - return undefined + // Is at least one mapping shown in the answer? + const someMappingIsShown = this.mappings.some(m => { + if(typeof m.hideInAnswer === "boolean"){ + return !m.hideInAnswer } + const isHidden = m.hideInAnswer.matchesProperties(currentProperties) + return !isHidden + } ) + // If all mappings are hidden for the current tags, we can safely assume that we should use the freeform key + const useFreeform = freeformValue !== undefined && (singleSelectedMapping === this.mappings.length || !someMappingIsShown) + if (useFreeform) { return new And([ new Tag(this.freeform.key, freeformValue), ...(this.freeform.addExtraTags ?? []), ]) - } else { + } else if(singleSelectedMapping) { return new And([ this.mappings[singleSelectedMapping].if, ...(this.mappings[singleSelectedMapping].addExtraTags ?? []), ]) + }else{ + console.log("TagRenderingConfig.ConstructSpecification has a weird fallback for", {freeformValue, singleSelectedMapping, multiSelectedMapping, currentProperties}) + return undefined } } } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index b739ebb036..f744e19fde 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -296,7 +296,8 @@ export default class ThemeViewState implements SpecialVisualizationState { ) this.initActors() - this.drawSpecialLayers(lastClick) + this.addLastClick(lastClick) + this.drawSpecialLayers() this.initHotkeys() this.miscSetup() console.log("State setup completed", this) @@ -424,10 +425,9 @@ export default class ThemeViewState implements SpecialVisualizationState { /** * Add the special layers to the map */ - private drawSpecialLayers(last_click: LastClickFeatureSource) { + private drawSpecialLayers() { type AddedByDefaultTypes = typeof Constants.added_by_default[number] const empty = [] - this.addLastClick(last_click) /** * A listing which maps the layerId onto the featureSource */ diff --git a/UI/InputElement/ValidatedInput.svelte b/UI/InputElement/ValidatedInput.svelte index 697e0f7dac..69db3df57f 100644 --- a/UI/InputElement/ValidatedInput.svelte +++ b/UI/InputElement/ValidatedInput.svelte @@ -6,29 +6,43 @@ import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"; import { Translation } from "../i18n/Translation"; import { createEventDispatcher, onDestroy } from "svelte"; + import {Validator} from "./Validator"; export let value: UIEventSource; // Internal state, only copied to 'value' so that no invalid values leak outside let _value = new UIEventSource(value.data ?? ""); onDestroy(value.addCallbackAndRunD(v => _value.setData(v ?? ""))); export let type: ValidatorType; - let validator = Validators.get(type); export let feedback: UIEventSource | undefined = undefined; + export let getCountry: () => string | undefined + let validator : Validator = Validators.get(type) + $: { + // The type changed -> reset some values + validator = Validators.get(type) + _value.setData("") + feedback = feedback?.setData(validator?.getFeedback(_value.data, getCountry)); + } + + onDestroy(value.addCallbackAndRun(v => { + if(v === undefined || v === ""){ + _value.setData("") + } + })) onDestroy(_value.addCallbackAndRun(v => { - if (validator.isValid(v)) { + if (validator.isValid(v, getCountry)) { feedback?.setData(undefined); value.setData(v); return; } value.setData(undefined); - feedback?.setData(validator.getFeedback(v)); + feedback?.setData(validator.getFeedback(v, getCountry)); })) if (validator === undefined) { throw "Not a valid type for a validator:" + type; } - const isValid = _value.map(v => validator.isValid(v)); + const isValid = _value.map(v => validator.isValid(v, getCountry)); let htmlElem: HTMLInputElement; diff --git a/UI/InputElement/Validator.ts b/UI/InputElement/Validator.ts index 5ad93b8829..4736b28d3c 100644 --- a/UI/InputElement/Validator.ts +++ b/UI/InputElement/Validator.ts @@ -44,9 +44,8 @@ export abstract class Validator { /** * Gets a piece of feedback. By default, validation. will be used, resulting in a generic 'not a valid '. * However, inheritors might overwrite this to give more specific feedback - * @param s */ - public getFeedback(s: string): Translation { + public getFeedback(s: string, requestCountry?: () => string): Translation { const tr = Translations.t.validation[this.name] if (tr !== undefined) { return tr["feedback"] diff --git a/UI/InputElement/Validators/EmailValidator.ts b/UI/InputElement/Validators/EmailValidator.ts index 04a4d82bd0..066c73765d 100644 --- a/UI/InputElement/Validators/EmailValidator.ts +++ b/UI/InputElement/Validators/EmailValidator.ts @@ -1,7 +1,8 @@ -import { Translation } from "../../i18n/Translation.js" +import {Translation} from "../../i18n/Translation.js" import Translations from "../../i18n/Translations.js" import * as emailValidatorLibrary from "email-validator" -import { Validator } from "../Validator" +import {Validator} from "../Validator" + export default class EmailValidator extends Validator { constructor() { super("email", "An email adress", "email") diff --git a/UI/InputElement/Validators/PhoneValidator.ts b/UI/InputElement/Validators/PhoneValidator.ts index 9ab3b183ac..97aa0d7585 100644 --- a/UI/InputElement/Validators/PhoneValidator.ts +++ b/UI/InputElement/Validators/PhoneValidator.ts @@ -1,12 +1,28 @@ -import { parsePhoneNumberFromString } from "libphonenumber-js" -import { Validator } from "../Validator" +import {parsePhoneNumberFromString} from "libphonenumber-js" +import {Validator} from "../Validator" +import {Translation} from "../../i18n/Translation"; +import Translations from "../../i18n/Translations"; export default class PhoneValidator extends Validator { constructor() { super("phone", "A phone number", "tel") } - isValid(str, country: () => string): boolean { + + getFeedback(s: string, requestCountry?: () => string): Translation { + const tr = Translations.t.validation.phone + const generic = tr.feedback + if(requestCountry){ + const country = requestCountry() + if(country){ + return tr.feedbackCountry.Subs({country}) + } + } + + return generic + } + + public isValid(str, country: () => string): boolean { if (str === undefined) { return false } @@ -20,13 +36,17 @@ export default class PhoneValidator extends Validator { return parsePhoneNumberFromString(str, countryCode)?.isValid() ?? false } - reformat = (str, country: () => string) => { + public reformat(str, country: () => string) { if (str.startsWith("tel:")) { str = str.substring("tel:".length) } + let countryCode = undefined + if(country){ + countryCode = country() + } return parsePhoneNumberFromString( str, - country()?.toUpperCase() as any + countryCode?.toUpperCase() as any )?.formatInternational() } } diff --git a/UI/OpeningHours/OpeningHours.ts b/UI/OpeningHours/OpeningHours.ts index a0ce4ec007..8bf3b195ee 100644 --- a/UI/OpeningHours/OpeningHours.ts +++ b/UI/OpeningHours/OpeningHours.ts @@ -498,7 +498,7 @@ export class OH { lat: tags._lat, lon: tags._lon, address: { - country_code: tags._country.toLowerCase(), + country_code: tags._country?.toLowerCase(), state: undefined, }, }, diff --git a/UI/OpeningHours/OpeningHoursVisualization.ts b/UI/OpeningHours/OpeningHoursVisualization.ts index be9acd840e..7a4273352d 100644 --- a/UI/OpeningHours/OpeningHoursVisualization.ts +++ b/UI/OpeningHours/OpeningHoursVisualization.ts @@ -10,6 +10,7 @@ import { VariableUiElement } from "../Base/VariableUIElement" import Table from "../Base/Table" import { Translation } from "../i18n/Translation" import { OsmConnection } from "../../Logic/Osm/OsmConnection" +import Loading from "../Base/Loading"; export default class OpeningHoursVisualization extends Toggle { private static readonly weekdays: Translation[] = [ @@ -29,6 +30,7 @@ export default class OpeningHoursVisualization extends Toggle { prefix = "", postfix = "" ) { + const country = tags.map(tags => tags._country) const ohTable = new VariableUiElement( tags .map((tags) => { @@ -66,12 +68,12 @@ export default class OpeningHoursVisualization extends Toggle { ), ]) } - }) + }, [country]) ) super( ohTable, - Translations.t.general.opening_hours.loadingCountry.Clone(), + new Loading(Translations.t.general.opening_hours.loadingCountry), tags.map((tgs) => tgs._country !== undefined) ) } @@ -160,7 +162,7 @@ export default class OpeningHoursVisualization extends Toggle { const weekdayStyles = [] for (let i = 0; i < 7; i++) { const day = OpeningHoursVisualization.weekdays[i].Clone() - day.SetClass("w-full h-full block") + day.SetClass("w-full h-full flex") const rangesForDay = ranges[i].map((range) => OpeningHoursVisualization.CreateRangeElem( diff --git a/UI/Popup/MoveWizard.ts b/UI/Popup/MoveWizard.ts index 294529d7fe..679fa14829 100644 --- a/UI/Popup/MoveWizard.ts +++ b/UI/Popup/MoveWizard.ts @@ -177,6 +177,7 @@ export default class MoveWizard extends Toggle { state.featureProperties.getStore(id).ping() currentStep.setData("moved") + state.mapProperties.location.setData(loc) }) const zoomInFurhter = t.zoomInFurther.SetClass("alert block m-6") return new Combine([ diff --git a/UI/Popup/QuestionBox.ts b/UI/Popup/QuestionBox.ts index 3118460cd0..b41477a64f 100644 --- a/UI/Popup/QuestionBox.ts +++ b/UI/Popup/QuestionBox.ts @@ -10,6 +10,8 @@ import Lazy from "../Base/Lazy" import { OsmServiceState } from "../../Logic/Osm/OsmConnection" /** + * @deprecated + * This element is getting stripped and is not used anymore * Generates all the questions, one by one */ export default class QuestionBox extends VariableUiElement { diff --git a/UI/Popup/TagRendering/FreeformInput.svelte b/UI/Popup/TagRendering/FreeformInput.svelte index 88df41b521..de0002c095 100644 --- a/UI/Popup/TagRendering/FreeformInput.svelte +++ b/UI/Popup/TagRendering/FreeformInput.svelte @@ -19,17 +19,20 @@ let dispatch = createEventDispatcher<{ "selected" }>(); onDestroy(value.addCallbackD(() => {dispatch("selected")})) + function getCountry() { + return tags.data["_country"] + }
{#if config.freeform.inline} - dispatch("selected")} + dispatch("selected")} type={config.freeform.type} {value}> {:else} - dispatch("selected")} + dispatch("selected")} type={config.freeform.type} {value}> {/if} diff --git a/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/UI/Popup/TagRendering/TagRenderingQuestion.svelte index d26468d669..a416a68e81 100644 --- a/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -16,6 +16,7 @@ import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"; import SpecialTranslation from "./SpecialTranslation.svelte"; import TagHint from "../TagHint.svelte"; + import Validators from "../../InputElement/Validators"; export let config: TagRenderingConfig; export let tags: UIEventSource>; @@ -34,9 +35,12 @@ let selectedMapping: number = undefined; let checkedMappings: boolean[]; $: { + // We received a new config -> reinit + console.log("Initing checkedMappings for", config) if (config.mappings?.length > 0 && (checkedMappings === undefined || checkedMappings?.length < config.mappings.length)) { checkedMappings = [...config.mappings.map(_ => false), false /*One element extra in case a freeform value is added*/]; } + freeformInput.setData(undefined) } let selectedTags: TagsFilter = undefined; @@ -54,9 +58,10 @@ $: { mappings = config.mappings?.filter(m => !mappingIsHidden(m)); try { - selectedTags = config?.constructChangeSpecification($freeformInput, selectedMapping, checkedMappings); + let freeformInputValue = $freeformInput + selectedTags = config?.constructChangeSpecification(freeformInputValue, selectedMapping, checkedMappings, tags.data); } catch (e) { - console.debug("Could not calculate changeSpecification:", e); + console.error("Could not calculate changeSpecification:", e); selectedTags = undefined; } } @@ -99,7 +104,7 @@ } ); freeformInput.setData(undefined); - selectedMapping = 0; + selectedMapping = undefined; selectedTags = undefined; change.CreateChangeDescriptions().then(changes => @@ -139,14 +144,14 @@ {#each config.mappings as mapping, i (mapping.then)} {#if !mappingIsHidden(mapping) } -
@@ -182,7 +182,7 @@ {/each} {#if config.freeform?.key}
{/if} + new ExtraLinkButton(state, layout.extraLink)}> - - Testmode - + + Testmode +
diff --git a/Utils/pngMapCreator.ts b/Utils/pngMapCreator.ts index 08e3b2cbb2..9abcb2a892 100644 --- a/Utils/pngMapCreator.ts +++ b/Utils/pngMapCreator.ts @@ -1,130 +1,53 @@ -import FeaturePipelineState from "../Logic/State/FeaturePipelineState" -import MinimapImplementation from "../UI/Base/MinimapImplementation" -import { UIEventSource } from "../Logic/UIEventSource" -import Loc from "../Models/Loc" -import ShowDataLayer from "../UI/ShowDataLayer/ShowDataLayer" -import { BBox } from "../Logic/BBox" -import Minimap from "../UI/Base/Minimap" -import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers" +import ThemeViewState from "../Models/ThemeViewState" +import SvelteUIElement from "../UI/Base/SvelteUIElement" +import MaplibreMap from "../UI/Map/MaplibreMap.svelte" import { Utils } from "../Utils" +import { UIEventSource } from "../Logic/UIEventSource" export interface PngMapCreatorOptions { - readonly divId: string readonly width: number readonly height: number - readonly scaling?: 1 | number - readonly dummyMode?: boolean } export class PngMapCreator { - private readonly _state: FeaturePipelineState | undefined + private static id = 0 private readonly _options: PngMapCreatorOptions + private readonly _state: ThemeViewState - constructor(state: FeaturePipelineState | undefined, options: PngMapCreatorOptions) { + constructor(state: ThemeViewState, options: PngMapCreatorOptions) { this._state = state - this._options = { ...options, scaling: options.scaling ?? 1 } - } - - /** - * Creates a minimap, waits till all needed tiles are loaded before returning - * @private - */ - private async createAndLoadMinimap(): Promise { - const state = this._state - const options = this._options - const baselayer = - AvailableBaseLayers.layerOverview.find( - (bl) => bl.id === state.layoutToUse.defaultBackgroundId - ) ?? AvailableBaseLayers.osmCarto - return new Promise((resolve) => { - const minimap = Minimap.createMiniMap({ - location: new UIEventSource(state.locationControl.data), // We remove the link between the old and the new UI-event source as moving the map while the export is running fucks up the screenshot - background: new UIEventSource(baselayer), - allowMoving: false, - onFullyLoaded: (_) => - window.setTimeout(() => { - resolve(minimap) - }, 250), - }) - const style = `width: ${options.width * options.scaling}mm; height: ${ - options.height * options.scaling - }mm;` - minimap.SetStyle(style) - minimap.AttachTo(options.divId) - }) + this._options = options } /** * Creates a base64-encoded PNG image * @constructor */ - public async CreatePng(format: "image"): Promise - public async CreatePng(format: "blob"): Promise - public async CreatePng(format: "image" | "blob"): Promise - public async CreatePng(format: "image" | "blob"): Promise { - // Lets first init the minimap and wait for all background tiles to load - const minimap = await this.createAndLoadMinimap() - const state = this._state - const dummyMode = this._options.dummyMode ?? false - return new Promise((resolve, reject) => { - // Next: we prepare the features. Only fully contained features are shown - minimap.leafletMap.addCallbackAndRunD(async (leaflet) => { - // Ping the featurepipeline to download what is needed - if (dummyMode) { - console.warn("Dummy mode is active - not loading map layers") - } else { - const bounds = BBox.fromLeafletBounds( - leaflet.getBounds().pad(0.1).pad(-state.layoutToUse.widenFactor) - ) - state.currentBounds.setData(bounds) - if (!state.featurePipeline.sufficientlyZoomed.data) { - console.warn("Not sufficiently zoomed!") - } - - if (state.featurePipeline.runningQuery.data) { - // A query is running! - // Let's wait for it to complete - console.log("Waiting for the query to complete") - await state.featurePipeline.runningQuery.AsPromise( - (isRunning) => !isRunning - ) - console.log("Query has completeted!") - } - - state.featurePipeline.GetTilesPerLayerWithin(bounds, (tile) => { - if (tile.layer.layerDef.id.startsWith("note_import")) { - // Don't export notes to import - return - } - new ShowDataLayer({ - features: tile, - leafletMap: minimap.leafletMap, - layerToShow: tile.layer.layerDef, - doShowLayer: tile.layer.isDisplayed, - state: undefined, - }) - }) - await Utils.waitFor(2000) - } - minimap - .TakeScreenshot(format) - .then(async (result) => { - const divId = this._options.divId - await Utils.waitFor(250) - document - .getElementById(divId) - .removeChild( - /*Will fetch the cached htmlelement:*/ minimap.ConstructElement() - ) - return resolve(result) - }) - .catch((failreason) => { - console.error("Could no make a screenshot due to ", failreason) - reject(failreason) - }) - }) - - state.AddAllOverlaysToMap(minimap.leafletMap) - }) + public async CreatePng(status: UIEventSource): Promise { + const div = document.createElement("div") + div.id = "mapdiv-" + PngMapCreator.id + PngMapCreator.id++ + const layout = this._state.layout + function setState(msg: string) { + status.setData(layout.id + ": " + msg) + } + setState("Initializing map") + const map = this._state.map + new SvelteUIElement(MaplibreMap, { map }) + .SetStyle( + "width: " + this._options.width + "mm; height: " + this._options.height + "mm" + ) + .AttachTo("extradiv") + setState("Waiting for the data") + await this._state.dataIsLoading.AsPromise((loading) => !loading) + setState("Waiting for styles to be fully loaded") + while (!map?.data?.isStyleLoaded()) { + await Utils.waitFor(250) + } + // Some extra buffer... + await Utils.waitFor(1000) + setState("Exporting png") + console.log("Loading for", this._state.layout.id, "is done") + return this._state.mapProperties.exportAsPng() } } diff --git a/Utils/svgToPdf.ts b/Utils/svgToPdf.ts index 3f324af901..c73b96d261 100644 --- a/Utils/svgToPdf.ts +++ b/Utils/svgToPdf.ts @@ -2,20 +2,21 @@ import jsPDF, { Matrix } from "jspdf" import { Translation, TypedTranslation } from "../UI/i18n/Translation" import { PngMapCreator } from "./pngMapCreator" import { AllKnownLayouts } from "../Customizations/AllKnownLayouts" -import { Store } from "../Logic/UIEventSource" -import "../assets/templates/Ubuntu-M-normal.js" -import "../assets/templates/Ubuntu-L-normal.js" -import "../assets/templates/UbuntuMono-B-bold.js" +import "../assets/fonts/Ubuntu-M-normal.js" +import "../assets/fonts/Ubuntu-L-normal.js" +import "../assets/fonts/UbuntuMono-B-bold.js" import { makeAbsolute, parseSVG } from "svg-path-parser" import Translations from "../UI/i18n/Translations" import { Utils } from "../Utils" import Constants from "../Models/Constants" import Hash from "../Logic/Web/Hash" import ThemeViewState from "../Models/ThemeViewState" +import { Store, UIEventSource } from "../Logic/UIEventSource" +import { FixedUiElement } from "../UI/Base/FixedUiElement" class SvgToPdfInternals { - private readonly doc: jsPDF private static readonly dummyDoc: jsPDF = new jsPDF() + private readonly doc: jsPDF private readonly matrices: Matrix[] = [] private readonly matricesInverted: Matrix[] = [] @@ -40,26 +41,6 @@ class SvgToPdfInternals { this.currentMatrixInverted = this.doc.unitMatrix } - applyMatrices(): void { - let multiplied = this.doc.unitMatrix - let multipliedInv = this.doc.unitMatrix - for (const matrix of this.matrices) { - multiplied = this.doc.matrixMult(multiplied, matrix) - } - for (const matrix of this.matricesInverted) { - multipliedInv = this.doc.matrixMult(multiplied, matrix) - } - this.currentMatrix = multiplied - this.currentMatrixInverted = multipliedInv - } - - addMatrix(m: Matrix) { - this.matrices.push(m) - this.matricesInverted.push(m.inversed()) - this.doc.setCurrentTransformationMatrix(m) - this.applyMatrices() - } - public static extractMatrix(element: Element): Matrix { const t = element.getAttribute("transform") if (t === null) { @@ -107,22 +88,6 @@ class SvgToPdfInternals { return null } - public setTransform(element: Element): boolean { - const m = SvgToPdfInternals.extractMatrix(element) - if (m === null) { - return false - } - this.addMatrix(m) - return true - } - - public undoTransform(): void { - this.matrices.pop() - const i = this.matricesInverted.pop() - this.doc.setCurrentTransformationMatrix(i) - this.applyMatrices() - } - public static parseCss(styleContent: string, separator: string = ";"): Record { if (styleContent === undefined || styleContent === null) { return {} @@ -137,41 +102,36 @@ class SvgToPdfInternals { return r } - private drawRect(element: SVGRectElement) { - const x = Number(element.getAttribute("x")) - const y = Number(element.getAttribute("y")) - const width = Number(element.getAttribute("width")) - const height = Number(element.getAttribute("height")) - const ry = SvgToPdfInternals.attrNumber(element, "ry", false) ?? 0 - const rx = SvgToPdfInternals.attrNumber(element, "rx", false) ?? 0 - const css = SvgToPdfInternals.css(element) - if (css["fill-opacity"] !== "0" && css["fill"] !== "none") { - this.doc.setFillColor(css["fill"] ?? "black") - this.doc.roundedRect(x, y, width, height, rx, ry, "F") + static attrNumber(element: Element, name: string, recurseup: boolean = true): number { + const a = SvgToPdfInternals.attr(element, name, recurseup) + const n = parseFloat(a) + if (!isNaN(n)) { + return n } - if (css["stroke"] && css["stroke"] !== "none") { - this.doc.setLineWidth(Number(css["stroke-width"] ?? 1)) - this.doc.setDrawColor(css["stroke"] ?? "black") - this.doc.roundedRect(x, y, width, height, rx, ry, "S") - } - return + return undefined } - private drawCircle(element: SVGCircleElement) { - const x = Number(element.getAttribute("cx")) - const y = Number(element.getAttribute("cy")) - const r = Number(element.getAttribute("r")) - const css = SvgToPdfInternals.css(element) - if (css["fill-opacity"] !== "0" && css["fill"] !== "none") { - this.doc.setFillColor(css["fill"] ?? "black") - this.doc.circle(x, y, r, "F") - } - if (css["stroke"] && css["stroke"] !== "none") { - this.doc.setLineWidth(Number(css["stroke-width"] ?? 1)) - this.doc.setDrawColor(css["stroke"] ?? "black") - this.doc.circle(x, y, r, "S") - } - return + /** + * Helper function to calculate where the given point will end up. + * ALl the transforms of the parent elements are taking into account + * @param mapSpec + * @constructor + */ + static GetActualXY(mapSpec: SVGTSpanElement): { x: number; y: number } { + let runningM = SvgToPdfInternals.dummyDoc.unitMatrix + + let e: Element = mapSpec + do { + const m = SvgToPdfInternals.extractMatrix(e) + if (m !== null) { + runningM = SvgToPdfInternals.dummyDoc.matrixMult(runningM, m) + } + e = e.parentElement + } while (e !== null && e.parentElement != e) + + const x = SvgToPdfInternals.attrNumber(mapSpec, "x") + const y = SvgToPdfInternals.attrNumber(mapSpec, "y") + return runningM.applyToPoint({ x, y }) } private static attr( @@ -214,13 +174,119 @@ class SvgToPdfInternals { return css } - static attrNumber(element: Element, name: string, recurseup: boolean = true): number { - const a = SvgToPdfInternals.attr(element, name, recurseup) - const n = parseFloat(a) - if (!isNaN(n)) { - return n + applyMatrices(): void { + let multiplied = this.doc.unitMatrix + let multipliedInv = this.doc.unitMatrix + for (const matrix of this.matrices) { + multiplied = this.doc.matrixMult(multiplied, matrix) } - return undefined + for (const matrix of this.matricesInverted) { + multipliedInv = this.doc.matrixMult(multiplied, matrix) + } + this.currentMatrix = multiplied + this.currentMatrixInverted = multipliedInv + } + + addMatrix(m: Matrix) { + this.matrices.push(m) + this.matricesInverted.push(m.inversed()) + this.doc.setCurrentTransformationMatrix(m) + this.applyMatrices() + } + + public setTransform(element: Element): boolean { + const m = SvgToPdfInternals.extractMatrix(element) + if (m === null) { + return false + } + this.addMatrix(m) + return true + } + + public undoTransform(): void { + this.matrices.pop() + const i = this.matricesInverted.pop() + this.doc.setCurrentTransformationMatrix(i) + this.applyMatrices() + } + + public handleElement(element: SVGSVGElement | Element): void { + const isTransformed = this.setTransform(element) + try { + if (element.tagName === "tspan") { + if (element.childElementCount == 0) { + this.drawTspan(element) + } else { + for (let child of Array.from(element.children)) { + this.handleElement(child) + } + } + } + + if (element.tagName === "image") { + this.drawImage(element) + } + + if (element.tagName === "path") { + this.drawPath(element) + } + + if (element.tagName === "g" || element.tagName === "text") { + for (let child of Array.from(element.children)) { + this.handleElement(child) + } + } + + if (element.tagName === "rect") { + this.drawRect(element) + } + + if (element.tagName === "circle") { + this.drawCircle(element) + } + } catch (e) { + console.error("Could not handle element", element, "due to", e) + } + if (isTransformed) { + this.undoTransform() + } + } + + private drawRect(element: SVGRectElement) { + const x = Number(element.getAttribute("x")) + const y = Number(element.getAttribute("y")) + const width = Number(element.getAttribute("width")) + const height = Number(element.getAttribute("height")) + const ry = SvgToPdfInternals.attrNumber(element, "ry", false) ?? 0 + const rx = SvgToPdfInternals.attrNumber(element, "rx", false) ?? 0 + const css = SvgToPdfInternals.css(element) + if (css["fill-opacity"] !== "0" && css["fill"] !== "none") { + this.doc.setFillColor(css["fill"] ?? "black") + this.doc.roundedRect(x, y, width, height, rx, ry, "F") + } + if (css["stroke"] && css["stroke"] !== "none") { + this.doc.setLineWidth(Number(css["stroke-width"] ?? 1)) + this.doc.setDrawColor(css["stroke"] ?? "black") + this.doc.roundedRect(x, y, width, height, rx, ry, "S") + } + return + } + + private drawCircle(element: SVGCircleElement) { + const x = Number(element.getAttribute("cx")) + const y = Number(element.getAttribute("cy")) + const r = Number(element.getAttribute("r")) + const css = SvgToPdfInternals.css(element) + if (css["fill-opacity"] !== "0" && css["fill"] !== "none") { + this.doc.setFillColor(css["fill"] ?? "black") + this.doc.circle(x, y, r, "F") + } + if (css["stroke"] && css["stroke"] !== "none") { + this.doc.setLineWidth(Number(css["stroke-width"] ?? 1)) + this.doc.setDrawColor(css["stroke"] ?? "black") + this.doc.circle(x, y, r, "S") + } + return } private drawTspan(tspan: Element) { @@ -427,129 +493,43 @@ class SvgToPdfInternals { this.doc.fill() } } - - public handleElement(element: SVGSVGElement | Element): void { - const isTransformed = this.setTransform(element) - try { - if (element.tagName === "tspan") { - if (element.childElementCount == 0) { - this.drawTspan(element) - } else { - for (let child of Array.from(element.children)) { - this.handleElement(child) - } - } - } - - if (element.tagName === "image") { - this.drawImage(element) - } - - if (element.tagName === "path") { - this.drawPath(element) - } - - if (element.tagName === "g" || element.tagName === "text") { - for (let child of Array.from(element.children)) { - this.handleElement(child) - } - } - - if (element.tagName === "rect") { - this.drawRect(element) - } - - if (element.tagName === "circle") { - this.drawCircle(element) - } - } catch (e) { - console.error("Could not handle element", element, "due to", e) - } - if (isTransformed) { - this.undoTransform() - } - } - - /** - * Helper function to calculate where the given point will end up. - * ALl the transforms of the parent elements are taking into account - * @param mapSpec - * @constructor - */ - static GetActualXY(mapSpec: SVGTSpanElement): { x: number; y: number } { - let runningM = SvgToPdfInternals.dummyDoc.unitMatrix - - let e: Element = mapSpec - do { - const m = SvgToPdfInternals.extractMatrix(e) - if (m !== null) { - runningM = SvgToPdfInternals.dummyDoc.matrixMult(runningM, m) - } - e = e.parentElement - } while (e !== null && e.parentElement != e) - - const x = SvgToPdfInternals.attrNumber(mapSpec, "x") - const y = SvgToPdfInternals.attrNumber(mapSpec, "y") - return runningM.applyToPoint({ x, y }) - } } export interface SvgToPdfOptions { - getFreeDiv: () => string disableMaps?: false | true textSubstitutions?: Record beforePage?: (i: number) => void overrideLocation?: { lat: number; lon: number } } -export class SvgToPdfPage { +class SvgToPdfPage { + public readonly _svgRoot: SVGSVGElement private images: Record = {} private rects: Record = {} - public readonly _svgRoot: SVGSVGElement - public readonly currentState: Store private readonly importedTranslations: Record = {} private readonly layerTranslations: Record> = {} private readonly options: SvgToPdfOptions + /** + * Small indicator for humans + * @private + */ + private readonly _state: UIEventSource + private _isPrepared = false + private state: UIEventSource - constructor(page: string, options?: SvgToPdfOptions) { + constructor(page: string, state: UIEventSource, options?: SvgToPdfOptions) { + this._state = state this.options = options ?? {} const parser = new DOMParser() const xmlDoc = parser.parseFromString(page, "image/svg+xml") this._svgRoot = xmlDoc.getElementsByTagName("svg")[0] } - private loadImage(element: Element): Promise { - const xlink = element.getAttribute("xlink:href") - let img = document.createElement("img") - - if (xlink.startsWith("data:image/svg+xml;")) { - const base64src = xlink - let svgXml = atob( - base64src.substring(base64src.indexOf(";base64,") + ";base64,".length) - ) - const parser = new DOMParser() - const xmlDoc = parser.parseFromString(svgXml, "text/xml") - const svgRoot = xmlDoc.getElementsByTagName("svg")[0] - const svgWidthStr = svgRoot.getAttribute("width") - const svgHeightStr = svgRoot.getAttribute("height") - const svgWidth = parseFloat(svgWidthStr) - const svgHeight = parseFloat(svgHeightStr) - if (!svgWidthStr.endsWith("px")) { - svgRoot.setAttribute("width", svgWidth + "px") - } - if (!svgHeightStr.endsWith("px")) { - svgRoot.setAttribute("height", svgHeight + "px") - } - img.src = "data:image/svg+xml;base64," + btoa(svgRoot.outerHTML) - } else { - img.src = xlink - } - - this.images[xlink] = img - return new Promise((resolve) => { - img.onload = (_) => { - resolve() - } + private static blobToBase64(blob): Promise { + return new Promise((resolve, _) => { + const reader = new FileReader() + reader.onloadend = () => resolve(reader.result) + reader.readAsDataURL(blob) }) } @@ -628,166 +608,6 @@ export class SvgToPdfPage { } } - private _isPrepared = false - - private async prepareMap(mapSpec: SVGTSpanElement): Promise { - // Upper left point of the tspan - const { x, y } = SvgToPdfInternals.GetActualXY(mapSpec) - - let textElement: Element = mapSpec - // We recurse up to get the actual, full specification - while (textElement.tagName !== "text") { - textElement = textElement.parentElement - } - const spec = textElement.textContent - const match = spec.match(/\$map\(([^)]+)\)$/) - if (match === null) { - throw "Invalid mapspec:" + spec - } - const params = SvgToPdfInternals.parseCss(match[1], ",") - - let smallestRect: SVGRectElement = undefined - let smallestSurface: number = undefined - // We iterate over all the rectangles and pick the smallest (by surface area) that contains the upper left point of the tspan - for (const id in this.rects) { - const rect = this.rects[id] - const rx = SvgToPdfInternals.attrNumber(rect, "x") - const ry = SvgToPdfInternals.attrNumber(rect, "y") - const w = SvgToPdfInternals.attrNumber(rect, "width") - const h = SvgToPdfInternals.attrNumber(rect, "height") - const inBounds = rx <= x && x <= rx + w && ry <= y && y <= ry + h - if (!inBounds) { - continue - } - const surface = w * h - if (smallestSurface === undefined || smallestSurface > surface) { - smallestSurface = surface - smallestRect = rect - } - } - - if (smallestRect === undefined) { - throw ( - "No rectangle found around " + - spec + - ". Draw a rectangle around it, the map will be projected on that one" - ) - } - - const svgImage = document.createElement("image") - svgImage.setAttribute("x", smallestRect.getAttribute("x")) - svgImage.setAttribute("y", smallestRect.getAttribute("y")) - const width = SvgToPdfInternals.attrNumber(smallestRect, "width") - const height = SvgToPdfInternals.attrNumber(smallestRect, "height") - svgImage.setAttribute("width", "" + width) - svgImage.setAttribute("height", "" + height) - - let layout = AllKnownLayouts.allKnownLayouts.get(params["theme"]) - if (layout === undefined) { - console.error("Could not show map with parameters", params) - throw ( - "Theme not found:" + params["theme"] + ". Use theme: to define which theme to use. " - ) - } - layout.widenFactor = 0 - layout.overpassTimeout = 600 - layout.defaultBackgroundId = params["background"] ?? layout.defaultBackgroundId - for (const paramsKey in params) { - if (paramsKey.startsWith("layer-")) { - const layerName = paramsKey.substring("layer-".length) - const key = params[paramsKey].toLowerCase().trim() - const layer = layout.layers.find((l) => l.id === layerName) - if (layer === undefined) { - throw "No layer found for " + paramsKey - } - if (key === "force") { - layer.minzoom = 0 - layer.minzoomVisible = 0 - } - } - } - const zoom = Number(params["zoom"] ?? params["z"] ?? 14) - - Hash.hash.setData(undefined) - // QueryParameters.ClearAll() - - const state = new ThemeViewState(layout) - state.mapProperties.location.setData({ - lat: this.options?.overrideLocation?.lat ?? Number(params["lat"] ?? 51.05016), - lon: this.options?.overrideLocation?.lon ?? Number(params["lon"] ?? 3.717842), - }) - state.mapProperties.zoom.setData(zoom) - - console.log("Params are", params, params["layers"] === "none") - - const fl = Array.from(state.layerState.filteredLayers.values()) - for (const filteredLayer of fl) { - if (params["layer-" + filteredLayer.layerDef.id] !== undefined) { - filteredLayer.isDisplayed.setData( - params["layer-" + filteredLayer.layerDef.id].trim().toLowerCase() !== "false" - ) - } else if (params["layers"] === "none") { - filteredLayer.isDisplayed.setData(false) - } else if (filteredLayer.layerDef.id.startsWith("note_import")) { - filteredLayer.isDisplayed.setData(false) - } - } - - for (const paramsKey in params) { - if (paramsKey.startsWith("layer-")) { - const layerName = paramsKey.substring("layer-".length) - const key = params[paramsKey].toLowerCase().trim() - const isDisplayed = key === "true" || key === "force" - const layer = fl.find((l) => l.layerDef.id === layerName) - console.log( - "Setting ", - layer?.layerDef?.id, - " to visibility", - isDisplayed, - "(minzoom:", - layer?.layerDef?.minzoomVisible, - layer?.layerDef?.minzoom, - ")" - ) - layer.isDisplayed.setData(isDisplayed) - if (key === "force") { - layer.layerDef.minzoom = 0 - layer.layerDef.minzoomVisible = 0 - layer.isDisplayed.addCallback((isDisplayed) => { - if (!isDisplayed) { - console.warn("Forcing layer " + paramsKey + " as true") - layer.isDisplayed.setData(true) - } - }) - } - } - } - - const pngCreator = new PngMapCreator(state, { - width, - height, - scaling: Number(params["scaling"] ?? 1.5), - divId: this.options.getFreeDiv(), - dummyMode: this.options.disableMaps, - }) - const png = await pngCreator.CreatePng("image") - - svgImage.setAttribute("xlink:href", png) - smallestRect.parentElement.insertBefore(svgImage, smallestRect) - await this.prepareElement(svgImage, []) - - const smallestRectCss = SvgToPdfInternals.parseCss(smallestRect.getAttribute("style")) - smallestRectCss["fill-opacity"] = "0" - smallestRect.setAttribute( - "style", - Object.keys(smallestRectCss) - .map((k) => k + ":" + smallestRectCss[k]) - .join(";") - ) - - textElement.parentElement.removeChild(textElement) - } - public async PrepareLanguage(language: string) { // Always fetch the remote data - it's cached anyway this.layerTranslations[language] = await Utils.downloadJsonCached( @@ -888,31 +708,219 @@ export class SvgToPdfPage { console.error("Could not get textFor from ", t, "for path", text) } } + + private loadImage(element: Element): Promise { + const xlink = element.getAttribute("xlink:href") + let img = document.createElement("img") + + if (xlink.startsWith("data:image/svg+xml;")) { + const base64src = xlink + let svgXml = atob( + base64src.substring(base64src.indexOf(";base64,") + ";base64,".length) + ) + const parser = new DOMParser() + const xmlDoc = parser.parseFromString(svgXml, "text/xml") + const svgRoot = xmlDoc.getElementsByTagName("svg")[0] + const svgWidthStr = svgRoot.getAttribute("width") + const svgHeightStr = svgRoot.getAttribute("height") + const svgWidth = parseFloat(svgWidthStr) + const svgHeight = parseFloat(svgHeightStr) + if (!svgWidthStr.endsWith("px")) { + svgRoot.setAttribute("width", svgWidth + "px") + } + if (!svgHeightStr.endsWith("px")) { + svgRoot.setAttribute("height", svgHeight + "px") + } + img.src = "data:image/svg+xml;base64," + btoa(svgRoot.outerHTML) + } else { + img.src = xlink + } + + this.images[xlink] = img + return new Promise((resolve) => { + img.onload = (_) => { + resolve() + } + }) + } + + private async prepareMap(mapSpec: SVGTSpanElement): Promise { + // Upper left point of the tspan + const { x, y } = SvgToPdfInternals.GetActualXY(mapSpec) + + let textElement: Element = mapSpec + // We recurse up to get the actual, full specification + while (textElement.tagName !== "text") { + textElement = textElement.parentElement + } + const spec = textElement.textContent + const match = spec.match(/\$map\(([^)]+)\)$/) + if (match === null) { + throw "Invalid mapspec:" + spec + } + const params = SvgToPdfInternals.parseCss(match[1], ",") + + let smallestRect: SVGRectElement = undefined + let smallestSurface: number = undefined + // We iterate over all the rectangles and pick the smallest (by surface area) that contains the upper left point of the tspan + for (const id in this.rects) { + const rect = this.rects[id] + const rx = SvgToPdfInternals.attrNumber(rect, "x") + const ry = SvgToPdfInternals.attrNumber(rect, "y") + const w = SvgToPdfInternals.attrNumber(rect, "width") + const h = SvgToPdfInternals.attrNumber(rect, "height") + const inBounds = rx <= x && x <= rx + w && ry <= y && y <= ry + h + if (!inBounds) { + continue + } + const surface = w * h + if (smallestSurface === undefined || smallestSurface > surface) { + smallestSurface = surface + smallestRect = rect + } + } + + if (smallestRect === undefined) { + throw ( + "No rectangle found around " + + spec + + ". Draw a rectangle around it, the map will be projected on that one" + ) + } + + const svgImage = document.createElement("image") + svgImage.setAttribute("x", smallestRect.getAttribute("x")) + svgImage.setAttribute("y", smallestRect.getAttribute("y")) + const width = SvgToPdfInternals.attrNumber(smallestRect, "width") + const height = SvgToPdfInternals.attrNumber(smallestRect, "height") + svgImage.setAttribute("width", "" + width) + svgImage.setAttribute("height", "" + height) + + let layout = AllKnownLayouts.allKnownLayouts.get(params["theme"]) + if (layout === undefined) { + console.error("Could not show map with parameters", params) + throw ( + "Theme not found:" + params["theme"] + ". Use theme: to define which theme to use. " + ) + } + layout.widenFactor = 0 + layout.overpassTimeout = 600 + layout.defaultBackgroundId = params["background"] ?? layout.defaultBackgroundId + for (const paramsKey in params) { + if (paramsKey.startsWith("layer-")) { + const layerName = paramsKey.substring("layer-".length) + const key = params[paramsKey].toLowerCase().trim() + const layer = layout.layers.find((l) => l.id === layerName) + if (layer === undefined) { + throw "No layer found for " + paramsKey + } + if (key === "force") { + layer.minzoom = 0 + layer.minzoomVisible = 0 + } + } + } + const zoom = Number(params["zoom"] ?? params["z"] ?? 14) + + Hash.hash.setData(undefined) + // QueryParameters.ClearAll() + const state = new ThemeViewState(layout) + state.mapProperties.location.setData({ + lat: this.options?.overrideLocation?.lat ?? Number(params["lat"] ?? 51.05016), + lon: this.options?.overrideLocation?.lon ?? Number(params["lon"] ?? 3.717842), + }) + state.mapProperties.zoom.setData(zoom) + + console.log("Params are", params, params["layers"] === "none") + + const fl = Array.from(state.layerState.filteredLayers.values()) + for (const filteredLayer of fl) { + if (params["layer-" + filteredLayer.layerDef.id] !== undefined) { + filteredLayer.isDisplayed.setData( + params["layer-" + filteredLayer.layerDef.id].trim().toLowerCase() !== "false" + ) + } else if (params["layers"] === "none") { + filteredLayer.isDisplayed.setData(false) + } else if (filteredLayer.layerDef.id.startsWith("note_import")) { + filteredLayer.isDisplayed.setData(false) + } + } + + for (const paramsKey in params) { + if (paramsKey.startsWith("layer-")) { + const layerName = paramsKey.substring("layer-".length) + const key = params[paramsKey].toLowerCase().trim() + const isDisplayed = key === "true" || key === "force" + const layer = fl.find((l) => l.layerDef.id === layerName) + console.log( + "Setting ", + layer?.layerDef?.id, + " to visibility", + isDisplayed, + "(minzoom:", + layer?.layerDef?.minzoomVisible, + layer?.layerDef?.minzoom, + ")" + ) + layer.isDisplayed.setData(isDisplayed) + if (key === "force") { + layer.layerDef.minzoom = 0 + layer.layerDef.minzoomVisible = 0 + layer.isDisplayed.addCallback((isDisplayed) => { + if (!isDisplayed) { + console.warn("Forcing layer " + paramsKey + " as true") + layer.isDisplayed.setData(true) + } + }) + } + } + } + console.log("Creating a map width ", width, height, params.scalingFactor) + const pngCreator = new PngMapCreator(state, { + width: width * 4, + height: height * 4, + }) + const png = await pngCreator.CreatePng(this._state) + svgImage.setAttribute("xlink:href", await SvgToPdfPage.blobToBase64(png)) + smallestRect.parentElement.insertBefore(svgImage, smallestRect) + await this.prepareElement(svgImage, []) + const smallestRectCss = SvgToPdfInternals.parseCss(smallestRect.getAttribute("style")) + smallestRectCss["fill-opacity"] = "0" + smallestRect.setAttribute( + "style", + Object.keys(smallestRectCss) + .map((k) => k + ":" + smallestRectCss[k]) + .join(";") + ) + + textElement.parentElement.removeChild(textElement) + } } export class SvgToPdf { public static readonly templates: Record< - string, + "flyer_a4" | "poster_a3" | "poster_a2", { pages: string[]; description: string | Translation } > = { flyer_a4: { pages: [ - "/assets/templates/MapComplete-flyer.svg", - "/assets/templates/MapComplete-flyer.back.svg", + "./assets/templates/MapComplete-flyer.svg", + "./assets/templates/MapComplete-flyer.back.svg", ], description: Translations.t.flyer.description, }, poster_a3: { - pages: ["/assets/templates/MapComplete-poster-a3.svg"], + pages: ["./assets/templates/MapComplete-poster-a3.svg"], description: "A basic A3 poster (similar to the flyer)", }, poster_a2: { - pages: ["/assets/templates/MapComplete-poster-a2.svg"], + pages: ["./assets/templates/MapComplete-poster-a2.svg"], description: "A basic A2 poster (similar to the flyer); scaled up from the A3 poster", }, } + public readonly status: Store + public readonly _status: UIEventSource private readonly _title: string - private readonly _pages: SvgToPdfPage[] constructor(title: string, pages: string[], options?: SvgToPdfOptions) { @@ -926,24 +934,34 @@ export class SvgToPdf { ).length options.textSubstitutions["mapCount"] = mapCount - this._pages = pages.map((page) => new SvgToPdfPage(page, options)) + const state = new UIEventSource("Initializing...") + this.status = state + this._status = state + this._pages = pages.map((page) => new SvgToPdfPage(page, state, options)) } public async ConvertSvg(language: string): Promise { + console.log("Building svg...") const firstPage = this._pages[0]._svgRoot const width = SvgToPdfInternals.attrNumber(firstPage, "width") const height = SvgToPdfInternals.attrNumber(firstPage, "height") const mode = width > height ? "landscape" : "portrait" await this.Prepare() + console.log("Global prepare done") for (const page of this._pages) { await page.Prepare() await page.PrepareLanguage(language) } + this._status.setData("Maps are rendered, building pdf") + new FixedUiElement("").AttachTo("extradiv") + console.log("Pages are prepared") + const doc = new jsPDF(mode, undefined, [width, height]) doc.advancedAPI((advancedApi) => { for (let i = 0; i < this._pages.length; i++) { + console.log("Rendering page", i) if (i > 0) { const page = this._pages[i]._svgRoot const width = SvgToPdfInternals.attrNumber(page, "width") @@ -967,6 +985,7 @@ export class SvgToPdf { this._pages[i].drawPage(advancedApi, i, language) } }) + console.log("Exporting...") await doc.save(this._title + "." + language + ".pdf") } diff --git a/assets/themes/natuurpunt/natuurpunt.css b/assets/themes/natuurpunt/natuurpunt.css index 10e5f614bd..09486cc70c 100644 --- a/assets/themes/natuurpunt/natuurpunt.css +++ b/assets/themes/natuurpunt/natuurpunt.css @@ -13,7 +13,6 @@ --popup-border: white; --shadow-color: #00000066; --variable-title-height: 0px; /* Set by javascript */ - --return-to-the-map-height: 2em; --image-carousel-height: 350px; } diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index b1d4943490..74a5c95b03 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -1407,10 +1407,6 @@ video { border-bottom-width: 1px; } -.border-b-2 { - border-bottom-width: 2px; -} - .border-solid { border-style: solid; } @@ -1588,10 +1584,6 @@ video { padding-top: 0px; } -.pb-8 { - padding-bottom: 2rem; -} - .pl-5 { padding-left: 1.25rem; } @@ -1852,8 +1844,8 @@ video { :root { /* The main colour scheme of mapcomplete is configured here. - * For a custom styling, set 'customCss' in your layoutConfig and overwrite some of these. - */ + * For a custom styling, set 'customCss' in your layoutConfig and overwrite some of these. + */ /* Main color of the application: the background and text colours */ --background-color: white; /* Main text colour. Also styles some elements, such as the 'close popup'-button or 'back-arrow' (in mobile) */ @@ -1861,23 +1853,22 @@ video { /* A colour to indicate an error or warning */ --alert-color: #fee4d1; /** - * Base colour of interactive elements, mainly the 'subtle button' - * - */ + * Base colour of interactive elements, mainly the 'subtle button' + * + */ --subtle-detail-color: #dbeafe; --subtle-detail-color-contrast: black; --subtle-detail-color-light-contrast: lightgrey; /** - * A stronger variant of the 'subtle-detail-colour' - * Used as subtle button hover - */ + * A stronger variant of the 'subtle-detail-colour' + * Used as subtle button hover + */ --unsubtle-detail-color: #bfdbfe; --unsubtle-detail-color-contrast: black; --catch-detail-color: #3a3aeb; --catch-detail-color-contrast: white; --non-active-tab-svg: var(--foreground-color); --shadow-color: #00000066; - --return-to-the-map-height: 2em; --image-carousel-height: 350px; /* Technical variable to make some dynamic behaviour possible; set by javascript. */ --variable-title-height: 0px; diff --git a/index.css b/index.css index 61e14c301d..61ef17031c 100644 --- a/index.css +++ b/index.css @@ -12,486 +12,484 @@ @tailwind utilities; @layer utilities { - .z-above-map { - z-index: 10000; - } + .z-above-map { + z-index: 10000; + } - .z-above-controls { - z-index: 10001; - } + .z-above-controls { + z-index: 10001; + } - .w-160 { - width: 40rem; - } + .w-160 { + width: 40rem; + } - .bg-subtle { - background-color: var(--subtle-detail-color); - color: var(--subtle-detail-color-contrast); - } + .bg-subtle { + background-color: var(--subtle-detail-color); + color: var(--subtle-detail-color-contrast); + } - .bg-unsubtle { - background-color: var(--unsubtle-detail-color); - color: var(--unsubtle-detail-color-contrast); - } + .bg-unsubtle { + background-color: var(--unsubtle-detail-color); + color: var(--unsubtle-detail-color-contrast); + } - .bg-catch { - background-color: var(--catch-detail-color); - color: var(--catch-detail-color-contrast); - } + .bg-catch { + background-color: var(--catch-detail-color); + color: var(--catch-detail-color-contrast); + } - .rounded-left-full { - border-bottom-left-radius: 999rem; - border-top-left-radius: 999rem; - } + .rounded-left-full { + border-bottom-left-radius: 999rem; + border-top-left-radius: 999rem; + } - .rounded-right-full { - border-bottom-right-radius: 999rem; - border-top-right-radius: 999rem; - } + .rounded-right-full { + border-bottom-right-radius: 999rem; + border-top-right-radius: 999rem; + } } :root { - /* The main colour scheme of mapcomplete is configured here. - * For a custom styling, set 'customCss' in your layoutConfig and overwrite some of these. - */ + /* The main colour scheme of mapcomplete is configured here. + * For a custom styling, set 'customCss' in your layoutConfig and overwrite some of these. + */ - /* Main color of the application: the background and text colours */ - --background-color: white; - /* Main text colour. Also styles some elements, such as the 'close popup'-button or 'back-arrow' (in mobile) */ - --foreground-color: black; + /* Main color of the application: the background and text colours */ + --background-color: white; + /* Main text colour. Also styles some elements, such as the 'close popup'-button or 'back-arrow' (in mobile) */ + --foreground-color: black; - /* A colour to indicate an error or warning */ - --alert-color: #fee4d1; + /* A colour to indicate an error or warning */ + --alert-color: #fee4d1; - /** - * Base colour of interactive elements, mainly the 'subtle button' - * - */ - --subtle-detail-color: #dbeafe; - --subtle-detail-color-contrast: black; - --subtle-detail-color-light-contrast: lightgrey; + /** + * Base colour of interactive elements, mainly the 'subtle button' + * + */ + --subtle-detail-color: #dbeafe; + --subtle-detail-color-contrast: black; + --subtle-detail-color-light-contrast: lightgrey; - /** - * A stronger variant of the 'subtle-detail-colour' - * Used as subtle button hover - */ - --unsubtle-detail-color: #bfdbfe; - --unsubtle-detail-color-contrast: black; + /** + * A stronger variant of the 'subtle-detail-colour' + * Used as subtle button hover + */ + --unsubtle-detail-color: #bfdbfe; + --unsubtle-detail-color-contrast: black; - --catch-detail-color: #3a3aeb; - --catch-detail-color-contrast: white; + --catch-detail-color: #3a3aeb; + --catch-detail-color-contrast: white; - --non-active-tab-svg: var(--foreground-color); - --shadow-color: #00000066; + --non-active-tab-svg: var(--foreground-color); + --shadow-color: #00000066; - --return-to-the-map-height: 2em; - --image-carousel-height: 350px; + --image-carousel-height: 350px; - /* Technical variable to make some dynamic behaviour possible; set by javascript. */ - --variable-title-height: 0px; + /* Technical variable to make some dynamic behaviour possible; set by javascript. */ + --variable-title-height: 0px; } html, body { - height: 100%; - min-height: 100vh; - min-height: -webkit-fill-available; - margin: 0; - padding: 0; - background-color: var(--background-color); - color: var(--foreground-color); - font-family: "Helvetica Neue", Arial, sans-serif; + height: 100%; + min-height: 100vh; + min-height: -webkit-fill-available; + margin: 0; + padding: 0; + background-color: var(--background-color); + color: var(--foreground-color); + font-family: "Helvetica Neue", Arial, sans-serif; } svg, img { - box-sizing: content-box; - width: 100%; - height: 100%; + box-sizing: content-box; + width: 100%; + height: 100%; } .no-images img { - /* Used solely in 'imageAttribution' */ - display: none; + /* Used solely in 'imageAttribution' */ + display: none; } .text-white a { - /* Used solely in 'imageAttribution' */ - color: var(--background-color); + /* Used solely in 'imageAttribution' */ + color: var(--background-color); } - .weblate-link { - /* Weblate-links are the little translation icon next to translatable sentences. Due to their special nature, they are exempt from some rules */ + /* Weblate-links are the little translation icon next to translatable sentences. Due to their special nature, they are exempt from some rules */ } a { - color: var(--foreground-color); + color: var(--foreground-color); } .btn { - line-height: 1.25rem; - --tw-text-opacity: 1; - color: var(--catch-detail-color-contrast); - --tw-bg-opacity: 1; - background-color: var(--catch-detail-color); - display: inline-flex; - border-radius: 1.5rem; - padding-top: 0.75rem; - padding-bottom: 0.75rem; - padding-left: 1.25rem; - padding-right: 1.25rem; - font-size: large; - font-weight: bold; - transition: 100ms; - /*-- invisible border: rendered on hover*/ - border: 3px solid var(--unsubtle-detail-color); + line-height: 1.25rem; + --tw-text-opacity: 1; + color: var(--catch-detail-color-contrast); + --tw-bg-opacity: 1; + background-color: var(--catch-detail-color); + display: inline-flex; + border-radius: 1.5rem; + padding-top: 0.75rem; + padding-bottom: 0.75rem; + padding-left: 1.25rem; + padding-right: 1.25rem; + font-size: large; + font-weight: bold; + transition: 100ms; + /*-- invisible border: rendered on hover*/ + border: 3px solid var(--unsubtle-detail-color); } .btn:hover { - border: 3px solid var(--catch-detail-color); + border: 3px solid var(--catch-detail-color); } .btn-secondary { - background-color: var(--catch-detail-color); - filter: saturate(0.5); + background-color: var(--catch-detail-color); + filter: saturate(0.5); } .btn-secondary:hover { - background-color: var(--catch-detail-color); - filter: unset; + background-color: var(--catch-detail-color); + filter: unset; } .btn-disabled { - filter: saturate(0.3); - cursor: default; + filter: saturate(0.3); + cursor: default; } .btn-disabled:hover { - border: 3px solid var(--unsubtle-detail-color); + border: 3px solid var(--unsubtle-detail-color); } /* slider */ input[type="range"].vertical { - writing-mode: bt-lr; /* IE */ - -webkit-appearance: slider-vertical; /* Chromium */ - cursor: pointer; + writing-mode: bt-lr; /* IE */ + -webkit-appearance: slider-vertical; /* Chromium */ + cursor: pointer; } @-moz-document url-prefix() { - input[type="range"].elevator::-moz-range-thumb { - background-color: #00000000 !important; - background-image: url("/assets/svg/elevator_wheelchair.svg"); - width: 150px !important; - height: 30px !important; - border: 2px; - border-style: solid; - background-size: contain; - background-position: center center; - background-repeat: no-repeat; - cursor: pointer; - border-image: linear-gradient(to right, black 50%, transparent 50%) 100% 1; - padding-bottom: 5px; - } + input[type="range"].elevator::-moz-range-thumb { + background-color: #00000000 !important; + background-image: url("/assets/svg/elevator_wheelchair.svg"); + width: 150px !important; + height: 30px !important; + border: 2px; + border-style: solid; + background-size: contain; + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + border-image: linear-gradient(to right, black 50%, transparent 50%) 100% 1; + padding-bottom: 5px; + } } .rounded-left-full { - border-bottom-left-radius: 999rem; - border-top-left-radius: 999rem; + border-bottom-left-radius: 999rem; + border-top-left-radius: 999rem; } .rounded-right-full { - border-bottom-right-radius: 999rem; - border-top-right-radius: 999rem; + border-bottom-right-radius: 999rem; + border-top-right-radius: 999rem; } .link-underline a { - text-decoration: underline 1px var(--foreground-color); + text-decoration: underline 1px var(--foreground-color); } a.link-underline { - text-decoration: underline 1px var(--foreground-color); + text-decoration: underline 1px var(--foreground-color); } .link-no-underline a { - text-decoration: none; + text-decoration: none; } li { - margin-left: 0.5em; - padding-left: 0.2em; - margin-top: 0.1em; + margin-left: 0.5em; + padding-left: 0.2em; + margin-top: 0.1em; } h2 { - font-size: large; - margin-top: 0.5em; - margin-bottom: 0.3em; - font-weight: bold; + font-size: large; + margin-top: 0.5em; + margin-bottom: 0.3em; + font-weight: bold; } h3 { - font-size: larger; - margin-top: 0.6em; - margin-bottom: 0; - font-weight: bold; + font-size: larger; + margin-top: 0.6em; + margin-bottom: 0; + font-weight: bold; } h3 { - font-size: larger; - margin-top: 0.6em; - margin-bottom: 0; - font-weight: bolder; + font-size: larger; + margin-top: 0.6em; + margin-bottom: 0; + font-weight: bolder; } p { - padding-top: 0.1em; + padding-top: 0.1em; } li::marker { - content: "•"; + content: "•"; } .subtle-background { - background: var(--subtle-detail-color); - color: var(--subtle-detail-color-contrast); + background: var(--subtle-detail-color); + color: var(--subtle-detail-color-contrast); } .normal-background { - background: var(--background-color); - color: var(--foreground-color); + background: var(--background-color); + color: var(--foreground-color); } .subtle-lighter { - color: var(--subtle-detail-color-light-contrast); + color: var(--subtle-detail-color-light-contrast); } .border-attention-catch { - border: 5px solid var(--catch-detail-color); + border: 5px solid var(--catch-detail-color); } .border-attention { - border-color: var(--catch-detail-color); + border-color: var(--catch-detail-color); } .direction-svg svg path { - fill: var(--catch-detail-color) !important; + fill: var(--catch-detail-color) !important; } .block-ruby { - display: block ruby; + display: block ruby; } .disable-links a { - pointer-events: none; - text-decoration: none !important; - color: var(--subtle-detail-color-contrast) !important; + pointer-events: none; + text-decoration: none !important; + color: var(--subtle-detail-color-contrast) !important; } .enable-links a { - pointer-events: unset; - text-decoration: underline !important; - color: unset !important; + pointer-events: unset; + text-decoration: underline !important; + color: unset !important; } .disable-links a.must-link, .disable-links .must-link a { - /* Hide links if they are disabled */ - display: none; + /* Hide links if they are disabled */ + display: none; } .selected svg:not(.noselect *) path.selectable { - stroke: white !important; - stroke-width: 20px !important; - overflow: visible !important; - -webkit-animation: glowing-drop-shadow 1s ease-in-out infinite alternate; - -moz-animation: glowing-drop-shadow 1s ease-in-out infinite alternate; - animation: glowing-drop-shadow 1s ease-in-out infinite alternate; + stroke: white !important; + stroke-width: 20px !important; + overflow: visible !important; + -webkit-animation: glowing-drop-shadow 1s ease-in-out infinite alternate; + -moz-animation: glowing-drop-shadow 1s ease-in-out infinite alternate; + animation: glowing-drop-shadow 1s ease-in-out infinite alternate; } .selected svg { - overflow: visible !important; + overflow: visible !important; } @-webkit-keyframes glowing-drop-shadow { - from { - filter: drop-shadow(5px 5px 60px rgb(128 128 128 / 0.6)); - } - to { - filter: drop-shadow(5px 5px 80px rgb(0.5 0.5 0.5 / 0.8)); - } + from { + filter: drop-shadow(5px 5px 60px rgb(128 128 128 / 0.6)); + } + to { + filter: drop-shadow(5px 5px 80px rgb(0.5 0.5 0.5 / 0.8)); + } } /**************** GENERIC ****************/ .alert { - background-color: var(--alert-color); - color: var(--foreground-color); - font-weight: bold; - border-radius: 1em; - margin: 0.25em; - text-align: center; - padding: 0.15em 0.3em; + background-color: var(--alert-color); + color: var(--foreground-color); + font-weight: bold; + border-radius: 1em; + margin: 0.25em; + text-align: center; + padding: 0.15em 0.3em; } .invalid { - box-shadow: 0 0 10px #ff5353; - height: min-content; + box-shadow: 0 0 10px #ff5353; + height: min-content; } .shadow { - box-shadow: 0 0 10px var(--shadow-color); + box-shadow: 0 0 10px var(--shadow-color); } .title-font span { - font-size: xx-large !important; - font-weight: bold; + font-size: xx-large !important; + font-weight: bold; } .soft { - background-color: var(--subtle-detail-color); - color: var(--subtle-detail-color-contrast); - font-weight: bold; - border-radius: 1em; - margin: 0.25em; - text-align: center; - padding: 0.15em 0.3em; + background-color: var(--subtle-detail-color); + color: var(--subtle-detail-color-contrast); + font-weight: bold; + border-radius: 1em; + margin: 0.25em; + text-align: center; + padding: 0.15em 0.3em; } .subtle { - color: #999; + color: #999; } .link-underline .subtle a { - text-decoration: underline 1px #7193bb88; - color: #7193bb; + text-decoration: underline 1px #7193bb88; + color: #7193bb; } .thanks { - background-color: #43d904; - font-weight: bold; - border-radius: 1em; - margin: 0.25em; - text-align: center; - padding: 0.15em 0.3em; + background-color: #43d904; + font-weight: bold; + border-radius: 1em; + margin: 0.25em; + text-align: center; + padding: 0.15em 0.3em; } @keyframes slide { - /* This is the animation on the marker to add a new point - it slides through all the possible presets */ - from { - transform: translateX(0%); - } + /* This is the animation on the marker to add a new point - it slides through all the possible presets */ + from { + transform: translateX(0%); + } - to { - transform: translateX(calc(-100% + 42px)); - } + to { + transform: translateX(calc(-100% + 42px)); + } } - /***************** Info box (box containing features and questions ******************/ input { - color: var(--foreground-color); + color: var(--foreground-color); } .literal-code { - display: inline-block; - background-color: lightgray; - padding: 0.5em; - word-break: break-word; - color: black; - box-sizing: border-box; + display: inline-block; + background-color: lightgray; + padding: 0.5em; + word-break: break-word; + color: black; + box-sizing: border-box; } /** Switch layout **/ .small-image img { - height: 1em; - max-width: 1em; + height: 1em; + max-width: 1em; } .small-image { - height: 1em; - max-width: 1em; + height: 1em; + max-width: 1em; } .slideshow-item img { - height: var(--image-carousel-height); - width: unset; + height: var(--image-carousel-height); + width: unset; } .animate-height { - transition: max-height 0.5s ease-in-out; - overflow-y: hidden; + transition: max-height 0.5s ease-in-out; + overflow-y: hidden; } .zebra-table tr:nth-child(even) { - background-color: #f2f2f2; + background-color: #f2f2f2; } .glowing-shadow { - -webkit-animation: glowing 1s ease-in-out infinite alternate; - -moz-animation: glowing 1s ease-in-out infinite alternate; - animation: glowing 1s ease-in-out infinite alternate; + -webkit-animation: glowing 1s ease-in-out infinite alternate; + -moz-animation: glowing 1s ease-in-out infinite alternate; + animation: glowing 1s ease-in-out infinite alternate; } + @-webkit-keyframes glowing { - from { - box-shadow: 0 0 20px 10px #eaaf2588, inset 0 0 0px 1px #eaaf25; - } - to { - box-shadow: 0 0 20px 20px #eaaf2588, inset 0 0 5px 1px #eaaf25; - } + from { + box-shadow: 0 0 20px 10px #eaaf2588, inset 0 0 0px 1px #eaaf25; + } + to { + box-shadow: 0 0 20px 20px #eaaf2588, inset 0 0 5px 1px #eaaf25; + } } .mapping-icon-small-height { - /* A mapping icon type */ - height: 1.5rem; - margin-right: 0.5rem; - width: unset; + /* A mapping icon type */ + height: 1.5rem; + margin-right: 0.5rem; + width: unset; } .mapping-icon-medium-height { - /* A mapping icon type */ - height: 3rem; - margin-right: 0.5rem; - width: unset; + /* A mapping icon type */ + height: 3rem; + margin-right: 0.5rem; + width: unset; } .mapping-icon-large-height { - /* A mapping icon type */ - height: 5rem; - margin-right: 0.5rem; - width: unset; + /* A mapping icon type */ + height: 5rem; + margin-right: 0.5rem; + width: unset; } .mapping-icon-small { - /* A mapping icon type */ - width: 1.5rem; - max-height: 1.5rem; - margin-right: 0.5rem; + /* A mapping icon type */ + width: 1.5rem; + max-height: 1.5rem; + margin-right: 0.5rem; } .mapping-icon-medium { - /* A mapping icon type */ - width: 3rem; - max-height: 3rem; - margin-right: 1rem; - margin-left: 1rem; + /* A mapping icon type */ + width: 3rem; + max-height: 3rem; + margin-right: 1rem; + margin-left: 1rem; } .mapping-icon-large { - /* A mapping icon type */ - width: 6rem; - max-height: 5rem; - margin-top: 0.5rem; - margin-bottom: 0.5rem; - margin-right: 1.5rem; - margin-left: 1.5rem; + /* A mapping icon type */ + width: 6rem; + max-height: 5rem; + margin-top: 0.5rem; + margin-bottom: 0.5rem; + margin-right: 1.5rem; + margin-left: 1.5rem; } diff --git a/package-lock.json b/package-lock.json index cf08140bb4..e63a98d2ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,6 @@ "showdown": "^2.1.0", "svg-path-parser": "^1.1.0", "tailwindcss": "^3.1.8", - "togpx": "^0.5.4", "vite-node": "^0.28.3", "vitest": "^0.28.3", "wikibase-sdk": "^7.14.0", @@ -4301,23 +4300,6 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, - "node_modules/bops": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/bops/-/bops-0.0.6.tgz", - "integrity": "sha512-EWD8/Ei9o/h/wmR3w/YL/8dGKe4rSFHlaO8VNNcuXnjXjeTgxdcmhjPf9hRCYlqTrBPZbKaht+FxZKahcob5UQ==", - "dependencies": { - "base64-js": "0.0.2", - "to-utf8": "0.0.1" - } - }, - "node_modules/bops/node_modules/base64-js": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.2.tgz", - "integrity": "sha512-Pj9L87dCdGcKlSqPVUjD+q96pbIx1zQQLb2CUiWURfjiBELv84YX+0nGnKmyT/9KkC7PQk7UN1w+Al8bBozaxQ==", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -7352,14 +7334,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/jxon": { - "version": "2.0.0-beta.5", - "resolved": "https://registry.npmjs.org/jxon/-/jxon-2.0.0-beta.5.tgz", - "integrity": "sha512-Ot7muZ0v2cmgQ1k+e6bpNcz6E3q2zHssvzYubbKTk5nIEvBLqJfiS6/uivU2ujqKZQlORcjKqcyx6D9X6BEAkQ==", - "dependencies": { - "xmldom": "^0.1.21" - } - }, "node_modules/kdbush": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-1.0.1.tgz", @@ -10265,36 +10239,6 @@ "node": ">=0.12.0" } }, - "node_modules/to-utf8": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz", - "integrity": "sha512-zks18/TWT1iHO3v0vFp5qLKOG27m67ycq/Y7a7cTiRuUNlc4gf3HGnkRgMv0NyhnfTamtkYBJl+YeD1/j07gBQ==" - }, - "node_modules/togpx": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/togpx/-/togpx-0.5.4.tgz", - "integrity": "sha512-1LY9ZjBrCYbcWfD63ZaqRw53U0tkigGC9fOdhRaTZH6yrmhlQGbbsEVTyCFzagvbPO36sZuBz0SrEQ+paaCPiw==", - "dependencies": { - "concat-stream": "~1.0.1", - "jxon": "~2.0.0-beta.5", - "optimist": "~0.3.5", - "xmldom": "~0.1.17" - }, - "bin": { - "togpx": "togpx" - } - }, - "node_modules/togpx/node_modules/concat-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.0.1.tgz", - "integrity": "sha512-nAHFsgeRVVvZ+aB3S1gLeN73fQ+tdOcw075BHbXMbC6MY0h6nqAkEeqPVCw8kRuDJJZDvaUjxI4jZv2FD0Tl8A==", - "engines": [ - "node >= 0.8.0" - ], - "dependencies": { - "bops": "0.0.6" - } - }, "node_modules/topojson-client": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", @@ -12055,15 +11999,6 @@ "optional": true, "peer": true }, - "node_modules/xmldom": { - "version": "0.1.31", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", - "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==", - "deprecated": "Deprecated due to CVE-2021-21366 resolved in 0.5.0", - "engines": { - "node": ">=0.1" - } - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -15350,22 +15285,6 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, - "bops": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/bops/-/bops-0.0.6.tgz", - "integrity": "sha512-EWD8/Ei9o/h/wmR3w/YL/8dGKe4rSFHlaO8VNNcuXnjXjeTgxdcmhjPf9hRCYlqTrBPZbKaht+FxZKahcob5UQ==", - "requires": { - "base64-js": "0.0.2", - "to-utf8": "0.0.1" - }, - "dependencies": { - "base64-js": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.2.tgz", - "integrity": "sha512-Pj9L87dCdGcKlSqPVUjD+q96pbIx1zQQLb2CUiWURfjiBELv84YX+0nGnKmyT/9KkC7PQk7UN1w+Al8bBozaxQ==" - } - } - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -17635,14 +17554,6 @@ "safe-buffer": "^5.0.1" } }, - "jxon": { - "version": "2.0.0-beta.5", - "resolved": "https://registry.npmjs.org/jxon/-/jxon-2.0.0-beta.5.tgz", - "integrity": "sha512-Ot7muZ0v2cmgQ1k+e6bpNcz6E3q2zHssvzYubbKTk5nIEvBLqJfiS6/uivU2ujqKZQlORcjKqcyx6D9X6BEAkQ==", - "requires": { - "xmldom": "^0.1.21" - } - }, "kdbush": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-1.0.1.tgz", @@ -19802,32 +19713,6 @@ } } }, - "to-utf8": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz", - "integrity": "sha512-zks18/TWT1iHO3v0vFp5qLKOG27m67ycq/Y7a7cTiRuUNlc4gf3HGnkRgMv0NyhnfTamtkYBJl+YeD1/j07gBQ==" - }, - "togpx": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/togpx/-/togpx-0.5.4.tgz", - "integrity": "sha512-1LY9ZjBrCYbcWfD63ZaqRw53U0tkigGC9fOdhRaTZH6yrmhlQGbbsEVTyCFzagvbPO36sZuBz0SrEQ+paaCPiw==", - "requires": { - "concat-stream": "~1.0.1", - "jxon": "~2.0.0-beta.5", - "optimist": "~0.3.5", - "xmldom": "~0.1.17" - }, - "dependencies": { - "concat-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.0.1.tgz", - "integrity": "sha512-nAHFsgeRVVvZ+aB3S1gLeN73fQ+tdOcw075BHbXMbC6MY0h6nqAkEeqPVCw8kRuDJJZDvaUjxI4jZv2FD0Tl8A==", - "requires": { - "bops": "0.0.6" - } - } - } - }, "topojson-client": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", @@ -21167,11 +21052,6 @@ "optional": true, "peer": true }, - "xmldom": { - "version": "0.1.31", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", - "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==" - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/test.ts b/test.ts index 6f2e3c21b1..d467fe3728 100644 --- a/test.ts +++ b/test.ts @@ -5,12 +5,14 @@ import Combine from "./UI/Base/Combine" import SpecialVisualizations from "./UI/SpecialVisualizations" import InputHelpers from "./UI/InputElement/InputHelpers" import BaseUIElement from "./UI/BaseUIElement" -import { ImmutableStore, UIEventSource } from "./Logic/UIEventSource" +import { UIEventSource } from "./Logic/UIEventSource" import { VariableUiElement } from "./UI/Base/VariableUIElement" import { FixedUiElement } from "./UI/Base/FixedUiElement" import Title from "./UI/Base/Title" import SvelteUIElement from "./UI/Base/SvelteUIElement" import ValidatedInput from "./UI/InputElement/ValidatedInput.svelte" +import { SvgToPdf } from "./Utils/svgToPdf" +import { Utils } from "./Utils" function testspecial() { const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) @@ -47,6 +49,17 @@ function testinput() { new Combine(els).SetClass("flex flex-col").AttachTo("maindiv") } +async function testPdf() { + const svgs = await Promise.all( + SvgToPdf.templates["flyer_a4"].pages.map((url) => Utils.download(url)) + ) + console.log("Building svg") + const pdf = new SvgToPdf("Test", svgs, {}) + new VariableUiElement(pdf.status).AttachTo("maindiv") + await pdf.ConvertSvg("nl") +} + +testPdf().then((_) => console.log("All done")) //testinput() /*/ testspecial() From e0527e964702427014220e2978680e3e6a7cc774 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 5 May 2023 11:00:31 +0200 Subject: [PATCH 104/257] Fix: fill in freeform value if already set --- UI/InputElement/ValidatedInput.svelte | 2 +- .../TagRendering/TagRenderingQuestion.svelte | 18 ++++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/UI/InputElement/ValidatedInput.svelte b/UI/InputElement/ValidatedInput.svelte index f0bf5b715a..e85dbaaf1d 100644 --- a/UI/InputElement/ValidatedInput.svelte +++ b/UI/InputElement/ValidatedInput.svelte @@ -19,7 +19,7 @@ $: { // The type changed -> reset some values validator = Validators.get(type) - _value.setData("") + _value.setData(value.data ?? "") feedback = feedback?.setData(validator?.getFeedback(_value.data, getCountry)); } diff --git a/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/UI/Popup/TagRendering/TagRenderingQuestion.svelte index 06874edb5e..26fab817eb 100644 --- a/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -27,13 +27,7 @@ export let layer: LayerConfig; // Will be bound if a freeform is available - let freeformInput = new UIEventSource(undefined); - onDestroy(tags.addCallbackAndRunD(tags => { - // initialize with the previous value - if (config.freeform?.key) { - freeformInput.setData(tags[config.freeform.key]); - } - })); + let freeformInput = new UIEventSource(tags?.[config.freeform?.key]); let selectedMapping: number = undefined; let checkedMappings: boolean[]; $: { @@ -44,20 +38,20 @@ return m.hideInAnswer.matchesProperties(tags.data) }) // We received a new config -> reinit - console.log("Initing checkedMappings for", config) if (config.mappings?.length > 0 && (checkedMappings === undefined || checkedMappings?.length < config.mappings.length)) { checkedMappings = [...config.mappings.map(_ => false), false /*One element extra in case a freeform value is added*/]; } - freeformInput.setData(undefined) + if (config.freeform?.key) { + freeformInput.setData(tags.data[config.freeform.key]); + }else{ + freeformInput.setData(undefined) + } } let selectedTags: TagsFilter = undefined; let mappings: Mapping[] = config?.mappings; let searchTerm: Store = new UIEventSource("") - $:{ - console.log("Seachterm:", $searchTerm) - } $: { try { From 55e12c32e5ba21e7f948025fb851c286e8f87148 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 5 May 2023 16:12:28 +0200 Subject: [PATCH 105/257] Don't show irrelevant title icons --- Logic/State/UserRelatedState.ts | 1 + UI/BigComponents/SelectedElementView.svelte | 3 +-- assets/layers/icons/icons.json | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index 4f0c4e391d..8c9865cff5 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -251,6 +251,7 @@ export default class UserRelatedState { _theme: layout?.id, _backend: this.osmConnection.Backend(), _applicationOpened: new Date().toISOString(), + _supports_sharing: window.navigator.share ? "yes" : "no" }) const osmConnection = this.osmConnection diff --git a/UI/BigComponents/SelectedElementView.svelte b/UI/BigComponents/SelectedElementView.svelte index e5607975d2..9bf83d432c 100644 --- a/UI/BigComponents/SelectedElementView.svelte +++ b/UI/BigComponents/SelectedElementView.svelte @@ -20,7 +20,6 @@ onDestroy(tags.addCallbackAndRun(tags => { _tags = tags; })); - console.log(layer.titleIcons.map(tr => tr.id)); let _metatags: Record; onDestroy(state.userRelatedState.preferencesAsTags.addCallbackAndRun(tags => { @@ -41,7 +40,7 @@
{#each layer.titleIcons as titleIconConfig} - {#if titleIconConfig.IsKnown(_tags)} + {#if ( titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties(_tags) ?? true) && titleIconConfig.IsKnown(_tags)}
diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index 7b420218b3..afcbb62f1c 100644 --- a/assets/layers/icons/icons.json +++ b/assets/layers/icons/icons.json @@ -100,7 +100,8 @@ "labels": [ "defaults" ], - "render": "{share_link()}" + "render": "{share_link()}", + "metacondition": "_supports_sharing=yes" }, { "id": "osmlink", @@ -123,4 +124,4 @@ } ], "mapRendering": null -} \ No newline at end of file +} From 7e3d0e6a79360433476da4780f5ae1f55ea5ab26 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 6 May 2023 01:23:55 +0200 Subject: [PATCH 106/257] Refactoring: cleanup, scroll questions into view, add placeholders --- UI/Base/ModalRight.svelte | 4 +- UI/BaseUIElement.ts | 25 +- UI/BigComponents/SelectedElementView.svelte | 108 +-- UI/InputElement/ValidatedInput.svelte | 9 +- UI/InputElement/Validator.ts | 4 + UI/Popup/AllTagsPanel.svelte | 6 - UI/Popup/EditableTagRendering.ts | 121 ---- UI/Popup/QuestionBox.ts | 139 ---- UI/Popup/TagRendering/FreeformInput.svelte | 9 +- .../TagRendering/TagRenderingEditable.svelte | 134 ++-- .../TagRendering/TagRenderingMapping.svelte | 4 +- .../TagRendering/TagRenderingQuestion.svelte | 2 - UI/Popup/TagRenderingAnswer.ts | 80 --- UI/Popup/TagRenderingQuestion.ts | 634 ------------------ UI/SpecialVisualizations.ts | 96 ++- Utils.ts | 23 +- 16 files changed, 217 insertions(+), 1181 deletions(-) delete mode 100644 UI/Popup/EditableTagRendering.ts delete mode 100644 UI/Popup/QuestionBox.ts delete mode 100644 UI/Popup/TagRenderingAnswer.ts delete mode 100644 UI/Popup/TagRenderingQuestion.ts diff --git a/UI/Base/ModalRight.svelte b/UI/Base/ModalRight.svelte index b0a11a3606..9ba2f4ec5e 100644 --- a/UI/Base/ModalRight.svelte +++ b/UI/Base/ModalRight.svelte @@ -8,8 +8,8 @@ const dispatch = createEventDispatcher<{ close }>(); -
-
+
+
dispatch("close")}> diff --git a/UI/BaseUIElement.ts b/UI/BaseUIElement.ts index 601218a91e..9a213b79f9 100644 --- a/UI/BaseUIElement.ts +++ b/UI/BaseUIElement.ts @@ -49,33 +49,10 @@ export default abstract class BaseUIElement { return this } - public ScrollToTop() { - this._constructedHtmlElement?.scrollTo(0, 0) - } - - public ScrollIntoView(options?: { onlyIfPartiallyHidden?: boolean }) { + public ScrollIntoView() { if (this._constructedHtmlElement === undefined) { return } - let alignToTop = true - if (options?.onlyIfPartiallyHidden) { - // Is the element completely in the view? - const parentRect = Utils.findParentWithScrolling( - this._constructedHtmlElement.parentElement - ).getBoundingClientRect() - const elementRect = this._constructedHtmlElement.getBoundingClientRect() - - // Check if the element is within the vertical bounds of the parent element - const topIsVisible = elementRect.top >= parentRect.top - const bottomIsVisible = elementRect.bottom <= parentRect.bottom - const inView = topIsVisible && bottomIsVisible - if (inView) { - return - } - if (topIsVisible) { - alignToTop = false - } - } this._constructedHtmlElement?.scrollIntoView({ behavior: "smooth", block: "start", diff --git a/UI/BigComponents/SelectedElementView.svelte b/UI/BigComponents/SelectedElementView.svelte index 9bf83d432c..5a5b732fc3 100644 --- a/UI/BigComponents/SelectedElementView.svelte +++ b/UI/BigComponents/SelectedElementView.svelte @@ -1,67 +1,71 @@ {#if _tags._deleted === "yes"} - + {:else} -
-
- -

- -

+
+
+
-
- {#each layer.titleIcons as titleIconConfig} - {#if ( titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties(_tags) ?? true) && titleIconConfig.IsKnown(_tags)} -
- + +

+ +

+ +
+ {#each layer.titleIcons as titleIconConfig} + {#if (titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties(_tags) ?? true) && titleIconConfig.IsKnown(_tags)} +
+ +
+ {/if} + {/each} +
+
- {/if} - {/each} -
- + state.selectedElement.setData(undefined)}/> +
+
+ {#each layer.tagRenderings as config (config.id)} + {#if (config.condition === undefined || config.condition.matchesProperties(_tags)) && (config.metacondition === undefined || config.metacondition.matchesProperties({..._tags, ..._metatags}))} + {#if config.IsKnown(_tags)} + + {/if} + {/if} + {/each} +
- -
- {#each layer.tagRenderings as config (config.id)} - {#if (config.condition === undefined || config.condition.matchesProperties(_tags)) && (config.metacondition === undefined || config.metacondition.matchesProperties({ ..._tags, ..._metatags }))} - {#if config.IsKnown(_tags)} - - {/if} - {/if} - {/each} -
- -
{/if} diff --git a/UI/InputElement/ValidatedInput.svelte b/UI/InputElement/ValidatedInput.svelte index e85dbaaf1d..3a621802c3 100644 --- a/UI/InputElement/ValidatedInput.svelte +++ b/UI/InputElement/ValidatedInput.svelte @@ -14,13 +14,16 @@ export let type: ValidatorType; export let feedback: UIEventSource | undefined = undefined; export let getCountry: () => string | undefined - + export let placeholder: string | Translation | undefined let validator : Validator = Validators.get(type) + let _placeholder = placeholder ?? validator?.getPlaceholder() ?? type + $: { // The type changed -> reset some values validator = Validators.get(type) _value.setData(value.data ?? "") feedback = feedback?.setData(validator?.getFeedback(_value.data, getCountry)); + _placeholder = placeholder ?? validator?.getPlaceholder() ?? type } onDestroy(_value.addCallbackAndRun(v => { @@ -50,10 +53,10 @@ {#if validator.textArea} - + {:else } - + {#if !$isValid} {/if} diff --git a/UI/InputElement/Validator.ts b/UI/InputElement/Validator.ts index 4736b28d3c..110821971a 100644 --- a/UI/InputElement/Validator.ts +++ b/UI/InputElement/Validator.ts @@ -52,6 +52,10 @@ export abstract class Validator { } } + public getPlaceholder(){ + return Translations.t.validation[this.name].description + } + public isValid(string: string, requestCountry?: () => string): boolean { return true } diff --git a/UI/Popup/AllTagsPanel.svelte b/UI/Popup/AllTagsPanel.svelte index e2e918bed7..6baa5009b0 100644 --- a/UI/Popup/AllTagsPanel.svelte +++ b/UI/Popup/AllTagsPanel.svelte @@ -63,9 +63,3 @@
- - diff --git a/UI/Popup/EditableTagRendering.ts b/UI/Popup/EditableTagRendering.ts deleted file mode 100644 index 6675721b1f..0000000000 --- a/UI/Popup/EditableTagRendering.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import TagRenderingQuestion from "./TagRenderingQuestion" -import Translations from "../i18n/Translations" -import Combine from "../Base/Combine" -import TagRenderingAnswer from "./TagRenderingAnswer" -import Toggle from "../Input/Toggle" -import BaseUIElement from "../BaseUIElement" -import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" -import { Unit } from "../../Models/Unit" -import Lazy from "../Base/Lazy" -import { FixedUiElement } from "../Base/FixedUiElement" -import { EditButton } from "./SaveButton" -import { UploadableTag } from "../../Logic/Tags/TagUtils" - -export default class EditableTagRendering extends Toggle { - constructor( - tags: UIEventSource, - configuration: TagRenderingConfig, - units: Unit[], - state, - options: { - editMode?: UIEventSource - innerElementClasses?: string - /* Classes applied _only_ on the rendered element, not on the question*/ - answerElementClasses?: string - /* Default will apply the tags to the relevant object, only use in special cases */ - createSaveButton?: (src: Store) => BaseUIElement - } - ) { - // The tagrendering is hidden if: - // - The answer is unknown. The questionbox will then show the question - // - There is a condition hiding the answer - const renderingIsShown = tags.map( - (tags) => - configuration.IsKnown(tags) && - (configuration?.condition?.matchesProperties(tags) ?? true) - ) - const editMode = options.editMode ?? new UIEventSource(false) - - super( - new Lazy(() => { - let rendering = EditableTagRendering.CreateRendering( - state, - tags, - configuration, - units, - editMode, - { - saveButtonConstructor: options?.createSaveButton, - answerElementClasses: options?.answerElementClasses, - } - ) - rendering.SetClass(options.innerElementClasses) - if (state?.featureSwitchIsDebugging?.data || state?.featureSwitchIsTesting?.data) { - rendering = new Combine([ - new FixedUiElement(configuration.id).SetClass("self-end subtle"), - rendering, - ]).SetClass("flex flex-col") - } - return rendering - }), - undefined, - renderingIsShown - ) - const self = this - editMode.addCallback((editing) => { - if (editing) { - self.ScrollIntoView() - } - }) - } - - private static CreateRendering( - state: any /*FeaturePipelineState*/, - tags: UIEventSource, - configuration: TagRenderingConfig, - units: Unit[], - editMode: UIEventSource, - options?: { - saveButtonConstructor?: (src: Store) => BaseUIElement - answerElementClasses?: string - } - ): BaseUIElement { - const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration, state) - answer.SetClass("w-full") - let rendering = answer - - if (configuration.question !== undefined && (state?.featureSwitchUserbadge?.data ?? true)) { - // We have a question and editing is enabled - - const question = new Lazy( - () => - new TagRenderingQuestion(tags, configuration, state, { - units: units, - cancelButton: Translations.t.general.cancel - .Clone() - .SetClass("btn btn-secondary") - .onClick(() => { - editMode.setData(false) - }), - saveButtonConstr: options?.saveButtonConstructor, - afterSave: () => { - editMode.setData(false) - }, - }) - ) - - const answerWithEditButton = new Combine([ - answer, - new EditButton(state?.osmConnection, () => { - editMode.setData(true) - question.ScrollIntoView({ - onlyIfPartiallyHidden: true, - }) - }), - ]).SetClass("flex justify-between w-full " + (options?.answerElementClasses ?? "")) - rendering = new Toggle(question, answerWithEditButton, editMode) - } - return rendering - } -} diff --git a/UI/Popup/QuestionBox.ts b/UI/Popup/QuestionBox.ts deleted file mode 100644 index b41477a64f..0000000000 --- a/UI/Popup/QuestionBox.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import TagRenderingQuestion from "./TagRenderingQuestion" -import Translations from "../i18n/Translations" -import Combine from "../Base/Combine" -import BaseUIElement from "../BaseUIElement" -import { VariableUiElement } from "../Base/VariableUIElement" -import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" -import { Unit } from "../../Models/Unit" -import Lazy from "../Base/Lazy" -import { OsmServiceState } from "../../Logic/Osm/OsmConnection" - -/** - * @deprecated - * This element is getting stripped and is not used anymore - * Generates all the questions, one by one - */ -export default class QuestionBox extends VariableUiElement { - constructor( - state, - options: { - tagsSource: UIEventSource - tagRenderings: TagRenderingConfig[] - units: Unit[] - showAllQuestionsAtOnce?: boolean | Store - } - ) { - const skippedQuestions: UIEventSource = new UIEventSource([]) - - const tagsSource = options.tagsSource - const units = options.units - - let focus: () => void = () => {} - - const tagRenderingQuestions = tagRenderings.map( - (tagRendering, i) => - new Lazy( - () => - new TagRenderingQuestion(tagsSource, tagRendering, state, { - units: units, - afterSave: () => { - // We save and indicate progress by pinging and recalculating - skippedQuestions.ping() - focus() - }, - cancelButton: Translations.t.general.skip - .Clone() - .SetClass("btn btn-secondary") - .onClick(() => { - skippedQuestions.data.push(i) - skippedQuestions.ping() - focus() - }), - }) - ) - ) - - tagsSource.map( - (tags) => { - if (tags === undefined) { - return undefined - } - for (let i = 0; i < tagRenderingQuestions.length; i++) { - let tagRendering = tagRenderings[i] - - if (skippedQuestions.data.indexOf(i) >= 0) { - continue - } - if (tagRendering.IsKnown(tags)) { - continue - } - if (tagRendering.condition) { - if (!tagRendering.condition.matchesProperties(tags)) { - // Filtered away by the condition, so it is kindof known - continue - } - } - - // this value is NOT known - this is the question we have to show! - return i - } - return undefined // The questions are depleted - }, - [skippedQuestions] - ) - - const questionsToAsk: Store = tagsSource.map( - (tags) => { - if (tags === undefined) { - return [] - } - const qs = [] - for (let i = 0; i < tagRenderingQuestions.length; i++) { - let tagRendering = tagRenderings[i] - - if (skippedQuestions.data.indexOf(i) >= 0) { - continue - } - if (tagRendering.IsKnown(tags)) { - continue - } - if (tagRendering.condition && !tagRendering.condition.matchesProperties(tags)) { - // Filtered away by the condition, so it is kindof known - continue - } - - // this value is NOT known - this is the question we have to show! - qs.push(tagRenderingQuestions[i]) - } - return qs - }, - [skippedQuestions] - ) - - super( - questionsToAsk.map( - (allQuestions) => { - const apiState: OsmServiceState = state.osmConnection.apiIsOnline.data - if (apiState !== "online" && apiState !== "unknown") { - return undefined - } - const els: BaseUIElement[] = [] - if ( - options.showAllQuestionsAtOnce === true || - options.showAllQuestionsAtOnce["data"] - ) { - els.push(...questionsToAsk.data) - } else { - els.push(allQuestions[0]) - } - - return new Combine(els).SetClass("block mb-8") - }, - [state.osmConnection.apiIsOnline] - ) - ) - - focus = () => this.ScrollIntoView() - } -} diff --git a/UI/Popup/TagRendering/FreeformInput.svelte b/UI/Popup/TagRendering/FreeformInput.svelte index c49d841f1b..fdee1d8513 100644 --- a/UI/Popup/TagRendering/FreeformInput.svelte +++ b/UI/Popup/TagRendering/FreeformInput.svelte @@ -14,6 +14,11 @@ export let tags: UIEventSource>; export let feature: Feature = undefined; + + let placeholder = config.freeform?.placeholder + $: { + placeholder = config.freeform?.placeholder + } let feedback: UIEventSource = new UIEventSource(undefined); @@ -29,11 +34,11 @@ {#if config.freeform.inline} dispatch("selected")} - type={config.freeform.type} {value}> + type={config.freeform.type} {placeholder} {value}> {:else} dispatch("selected")} - type={config.freeform.type} {value}> + type={config.freeform.type} {placeholder} {value}> {/if} diff --git a/UI/Popup/TagRendering/TagRenderingEditable.svelte b/UI/Popup/TagRendering/TagRenderingEditable.svelte index f0480fff71..e6e0ea4a3c 100644 --- a/UI/Popup/TagRendering/TagRenderingEditable.svelte +++ b/UI/Popup/TagRendering/TagRenderingEditable.svelte @@ -1,74 +1,90 @@
- {#if config.question} - {#if editMode} - - - - {:else} -
- - -
+ {#if config.question && $editingEnabled} + {#if editMode} + + + + {:else} +
+ + +
+ {/if} + {:else } + {/if} - {:else } - - {/if}
diff --git a/UI/Popup/TagRendering/TagRenderingMapping.svelte b/UI/Popup/TagRendering/TagRenderingMapping.svelte index a9fad70eb0..7df9060108 100644 --- a/UI/Popup/TagRendering/TagRenderingMapping.svelte +++ b/UI/Popup/TagRendering/TagRenderingMapping.svelte @@ -3,10 +3,8 @@ import SpecialTranslation from "./SpecialTranslation.svelte"; import type {SpecialVisualizationState} from "../../SpecialVisualization"; import type {Feature} from "geojson"; - import {Store, UIEventSource} from "../../../Logic/UIEventSource"; + import {UIEventSource} from "../../../Logic/UIEventSource"; import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; - import Locale from "../../i18n/Locale"; - import {onDestroy} from "svelte"; export let selectedElement: Feature export let tags: UIEventSource>; diff --git a/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/UI/Popup/TagRendering/TagRenderingQuestion.svelte index 26fab817eb..373f1013bb 100644 --- a/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -131,7 +131,6 @@
{/if} - {#if config.mappings?.length >= 8}
@@ -139,7 +138,6 @@
{/if} - {#if config.freeform?.key && !(mappings?.length > 0)} diff --git a/UI/Popup/TagRenderingAnswer.ts b/UI/Popup/TagRenderingAnswer.ts deleted file mode 100644 index 75ef9fd843..0000000000 --- a/UI/Popup/TagRenderingAnswer.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { UIEventSource } from "../../Logic/UIEventSource" -import { Utils } from "../../Utils" -import BaseUIElement from "../BaseUIElement" -import { VariableUiElement } from "../Base/VariableUIElement" -import { SubstitutedTranslation } from "../SubstitutedTranslation" -import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" -import Combine from "../Base/Combine" -import Img from "../Base/Img" -import { SpecialVisualizationState } from "../SpecialVisualization" - -/*** - * Displays the correct value for a known tagrendering - */ -export default class TagRenderingAnswer extends VariableUiElement { - constructor( - tagsSource: UIEventSource, - configuration: TagRenderingConfig, - state: SpecialVisualizationState, - contentClasses: string = "", - contentStyle: string = "", - options?: { - specialViz: Map - } - ) { - if (configuration === undefined) { - throw "Trying to generate a tagRenderingAnswer without configuration..." - } - UIEventSource - if (tagsSource === undefined) { - throw "Trying to generate a tagRenderingAnswer without tagSource..." - } - super( - tagsSource - .map((tags) => { - if (tags === undefined) { - return undefined - } - - if (configuration.condition) { - if (!configuration.condition.matchesProperties(tags)) { - return undefined - } - } - - const trs = Utils.NoNull(configuration.GetRenderValues(tags)) - if (trs.length === 0) { - return undefined - } - - const valuesToRender: BaseUIElement[] = trs.map((tr) => { - const text = new SubstitutedTranslation( - tr.then, - tagsSource, - state, - options?.specialViz - ) - if (tr.icon === undefined) { - return text - } - return new Combine([ - new Img(tr.icon).SetClass("mapping-icon-" + (tr.iconClass ?? "small")), - text, - ]).SetClass("flex items-center") - }) - if (valuesToRender.length === 1) { - return valuesToRender[0] - } else if (valuesToRender.length > 1) { - return new Combine(valuesToRender).SetClass("flex flex-col") - } - return undefined - }) - .map((element: BaseUIElement) => - element?.SetClass(contentClasses)?.SetStyle(contentStyle) - ) - ) - - this.SetClass("flex items-center flex-row text-lg link-underline") - this.SetStyle("word-wrap: anywhere;") - } -} diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts deleted file mode 100644 index 25df9686ac..0000000000 --- a/UI/Popup/TagRenderingQuestion.ts +++ /dev/null @@ -1,634 +0,0 @@ -import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource" -import Combine from "../Base/Combine" -import { InputElement, ReadonlyInputElement } from "../Input/InputElement" -import { FixedInputElement } from "../Input/FixedInputElement" -import { RadioButton } from "../Input/RadioButton" -import { Utils } from "../../Utils" -import CheckBoxes from "../Input/Checkboxes" -import InputElementMap from "../Input/InputElementMap" -import { SaveButton } from "./SaveButton" -import { VariableUiElement } from "../Base/VariableUIElement" -import Translations from "../i18n/Translations" -import { Translation } from "../i18n/Translation" -import { SubstitutedTranslation } from "../SubstitutedTranslation" -import { TagsFilter } from "../../Logic/Tags/TagsFilter" -import { Tag } from "../../Logic/Tags/Tag" -import { And } from "../../Logic/Tags/And" -import { TagUtils, UploadableTag } from "../../Logic/Tags/TagUtils" -import BaseUIElement from "../BaseUIElement" -import { DropDown } from "../Input/DropDown" -import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" -import TagRenderingConfig, { Mapping } from "../../Models/ThemeConfig/TagRenderingConfig" -import { Unit } from "../../Models/Unit" -import VariableInputElement from "../Input/VariableInputElement" -import Toggle from "../Input/Toggle" -import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" -import Title from "../Base/Title" -import { GeoOperations } from "../../Logic/GeoOperations" -import { SearchablePillsSelector } from "../Input/SearchableMappingsSelector" -import { OsmTags } from "../../Models/OsmFeature" - -/** - * @deprecated: getting stripped and getting ported - * Shows the question element. - * Note that the value _migh_ already be known, e.g. when selected or when changing the value - */ -export default class TagRenderingQuestion extends Combine { - constructor( - tags: UIEventSource & { id: string }>, - configuration: TagRenderingConfig, - state?: FeaturePipelineState, - options?: { - units?: Unit[] - afterSave?: () => void - cancelButton?: BaseUIElement - saveButtonConstr?: (src: Store) => BaseUIElement - } - ) { - const applicableMappingsSrc = Stores.ListStabilized( - tags.map((tags) => { - const applicableMappings: Mapping[] = [] - for (const mapping of configuration.mappings ?? []) { - if (mapping.hideInAnswer === true) { - continue - } - if (mapping.hideInAnswer === false || mapping.hideInAnswer === undefined) { - applicableMappings.push(mapping) - continue - } - const condition = mapping.hideInAnswer - const isShown = !condition.matchesProperties(tags) - if (isShown) { - applicableMappings.push(mapping) - } - } - return applicableMappings - }) - ) - - if (configuration === undefined) { - throw "A question is needed for a question visualization" - } - options = options ?? {} - const applicableUnit = (options.units ?? []).filter((unit) => - unit.isApplicableToKey(configuration.freeform?.key) - )[0] - const question = new Title( - new SubstitutedTranslation(configuration.question, tags, state).SetClass( - "question-text" - ), - 3 - ) - let questionHint = undefined - if (configuration.questionhint !== undefined) { - questionHint = new SubstitutedTranslation( - configuration.questionhint, - tags, - state - ).SetClass("font-bold subtle") - } - - const feedback = new UIEventSource(undefined) - const inputElement: ReadonlyInputElement = new VariableInputElement( - applicableMappingsSrc.map((applicableMappings) => { - return TagRenderingQuestion.GenerateInputElement( - state, - configuration, - applicableMappings, - applicableUnit, - tags, - feedback - ) - }) - ) - - if (options.saveButtonConstr === undefined) { - const save = () => { - const selection = TagUtils.FlattenMultiAnswer( - TagUtils.FlattenAnd(inputElement.GetValue().data, tags.data) - ) - if (selection) { - ;(state?.changes) - .applyAction( - new ChangeTagAction(tags.data.id, selection, tags.data, { - theme: state?.layoutToUse?.id ?? "unkown", - changeType: "answer", - }) - ) - .then((_) => { - console.log("Tagchanges applied") - }) - if (options.afterSave) { - options.afterSave() - } - } - } - - options.saveButtonConstr = (v) => new SaveButton(v, state?.osmConnection).onClick(save) - } - - const saveButton = new Combine([options.saveButtonConstr(inputElement.GetValue())]) - - super([ - question, - questionHint, - inputElement, - new VariableUiElement( - feedback.map((t) => - t - ?.SetStyle("padding-left: 0.75rem; padding-right: 0.75rem") - ?.SetClass("alert flex") - ) - ), - new Combine([options.cancelButton, saveButton]).SetClass( - "flex justify-end flex-wrap-reverse" - ), - new Toggle( - Translations.t.general.testing.SetClass("block alert"), - undefined, - state?.featureSwitchIsTesting - ), - ]) - - this.SetClass("question disable-links") - } - - private static GenerateInputElement( - state: FeaturePipelineState, - configuration: TagRenderingConfig, - applicableMappings: Mapping[], - applicableUnit: Unit, - tagsSource: UIEventSource, - feedback: UIEventSource - ): ReadonlyInputElement { - const hasImages = applicableMappings.findIndex((mapping) => mapping.icon !== undefined) >= 0 - let inputEls: InputElement[] - - const ifNotsPresent = applicableMappings.some((mapping) => mapping.ifnot !== undefined) - - if ( - applicableMappings.length > 8 && - (configuration.freeform?.type === undefined || - configuration.freeform?.type === "string") && - (!configuration.multiAnswer || configuration.freeform === undefined) - ) { - return TagRenderingQuestion.GenerateSearchableSelector( - state, - configuration, - applicableMappings, - tagsSource - ) - } - - // FreeForm input will be undefined if not present; will already contain a special input element if applicable - const ff = TagRenderingQuestion.GenerateFreeform( - state, - configuration, - applicableUnit, - tagsSource, - feedback - ) - - function allIfNotsExcept(excludeIndex: number): UploadableTag[] { - if (configuration.mappings === undefined || configuration.mappings.length === 0) { - return undefined - } - if (!ifNotsPresent) { - return [] - } - if (configuration.multiAnswer) { - // The multianswer will do the ifnot configuration themself - return [] - } - - const negativeMappings = [] - - for (let i = 0; i < applicableMappings.length; i++) { - const mapping = applicableMappings[i] - if (i === excludeIndex || mapping.ifnot === undefined) { - continue - } - negativeMappings.push(mapping.ifnot) - } - return Utils.NoNull(negativeMappings) - } - - if ( - applicableMappings.length < 8 || - configuration.multiAnswer || - (hasImages && applicableMappings.length < 16) || - ifNotsPresent - ) { - inputEls = (applicableMappings ?? []).map((mapping, i) => - TagRenderingQuestion.GenerateMappingElement( - state, - tagsSource, - mapping, - allIfNotsExcept(i) - ) - ) - inputEls = Utils.NoNull(inputEls) - } else { - const dropdown: InputElement = new DropDown( - "", - applicableMappings.map((mapping, i) => { - return { - value: new And([mapping.if, ...allIfNotsExcept(i)]), - shown: mapping.then.Subs(tagsSource.data), - } - }) - ) - - if (ff == undefined) { - return dropdown - } else { - inputEls = [dropdown] - } - } - - if (inputEls.length == 0) { - if (ff === undefined) { - throw "Error: could not generate a question: freeform and all mappings are undefined" - } - return ff - } - - if (ff) { - inputEls.push(ff) - } - - if (configuration.multiAnswer) { - return TagRenderingQuestion.GenerateMultiAnswer( - configuration, - inputEls, - ff, - applicableMappings.map((mp) => mp.ifnot) - ) - } else { - return new RadioButton(inputEls, { selectFirstAsDefault: false }) - } - } - - private static MappingToPillValue( - applicableMappings: Mapping[], - tagsSource: UIEventSource, - state: FeaturePipelineState - ): { - show: BaseUIElement - value: number - mainTerm: Record - searchTerms?: Record - original: Mapping - hasPriority?: Store - }[] { - const values: { - show: BaseUIElement - value: number - mainTerm: Record - searchTerms?: Record - original: Mapping - hasPriority?: Store - }[] = [] - const addIcons = applicableMappings.some((m) => m.icon !== undefined) - for (let i = 0; i < applicableMappings.length; i++) { - const mapping = applicableMappings[i] - const tr = mapping.then.Subs(tagsSource.data) - const patchedMapping = { - ...mapping, - iconClass: mapping.iconClass ?? `small-height`, - icon: mapping.icon ?? (addIcons ? "./assets/svg/none.svg" : undefined), - } - const fancy = TagRenderingQuestion.GenerateMappingContent( - patchedMapping, - tagsSource, - state - ).SetClass("normal-background") - values.push({ - show: fancy, - value: i, - mainTerm: tr.translations, - searchTerms: mapping.searchTerms, - original: mapping, - hasPriority: tagsSource.map((tags) => mapping.priorityIf?.matchesProperties(tags)), - }) - } - return values - } - - private static GenerateSearchableSelector( - state: FeaturePipelineState, - configuration: TagRenderingConfig, - applicableMappings: Mapping[], - tagsSource: UIEventSource, - options?: { - search: UIEventSource - } - ): InputElement { - const values = TagRenderingQuestion.MappingToPillValue( - applicableMappings, - tagsSource, - state - ) - - const searchValue: UIEventSource = - options?.search ?? new UIEventSource(undefined) - const ff = configuration.freeform - let onEmpty: BaseUIElement = undefined - if (ff !== undefined) { - onEmpty = new VariableUiElement( - searchValue.map((search) => configuration.render.Subs({ [ff.key]: search })) - ) - } - const mode = configuration.multiAnswer ? "select-many" : "select-one" - - const classes = "h-64 overflow-scroll" - const presetSearch = new SearchablePillsSelector(values, { - mode, - searchValue, - onNoMatches: onEmpty?.SetClass(classes).SetClass("flex justify-center items-center"), - searchAreaClass: classes, - }) - const fallbackTag = searchValue.map((s) => { - if (s === undefined || ff?.key === undefined) { - return undefined - } - return new Tag(ff.key, s) - }) - return new InputElementMap( - presetSearch, - (x0, x1) => { - if (x0 == x1) { - return true - } - if (x0 === undefined || x1 === undefined) { - return false - } - if (x0.and.length !== x1.and.length) { - return false - } - for (let i = 0; i < x0.and.length; i++) { - if (x1.and[i] != x0.and[i]) { - return false - } - } - return true - }, - (selected) => { - if ( - ff !== undefined && - searchValue.data?.length > 0 && - !presetSearch.someMatchFound.data - ) { - const t = fallbackTag.data - if (ff.addExtraTags) { - return new And([t, ...ff.addExtraTags]) - } - return new And([t]) - } - - if (selected === undefined || selected.length == 0) { - return undefined - } - - const tfs = Utils.NoNull( - applicableMappings.map((mapping, i) => { - if (selected.indexOf(i) >= 0) { - return mapping.if - } else { - return mapping.ifnot - } - }) - ) - return new And(tfs) - }, - (tf) => { - if (tf === undefined) { - return [] - } - const selected: number[] = [] - for (let i = 0; i < applicableMappings.length; i++) { - const mapping = applicableMappings[i] - if (tf.and.some((t) => mapping.if == t)) { - selected.push(i) - } - } - return selected - }, - [searchValue, presetSearch.someMatchFound] - ) - } - - private static GenerateMultiAnswer( - configuration: TagRenderingConfig, - elements: InputElement[], - freeformField: InputElement, - ifNotSelected: UploadableTag[] - ): InputElement { - const checkBoxes = new CheckBoxes(elements) - - const inputEl = new InputElementMap( - checkBoxes, - (t0, t1) => { - return t0?.shadows(t1) ?? false - }, - (indices) => { - if (indices.length === 0) { - return undefined - } - const tags: UploadableTag[] = indices.map((i) => elements[i].GetValue().data) - const oppositeTags: UploadableTag[] = [] - for (let i = 0; i < ifNotSelected.length; i++) { - if (indices.indexOf(i) >= 0) { - continue - } - const notSelected = ifNotSelected[i] - if (notSelected === undefined) { - continue - } - oppositeTags.push(notSelected) - } - tags.push(TagUtils.FlattenMultiAnswer(oppositeTags)) - return TagUtils.FlattenMultiAnswer(tags) - }, - (tags: UploadableTag) => { - // {key --> values[]} - - const presentTags = TagUtils.SplitKeys([tags]) - const indices: number[] = [] - // We also collect the values that have to be added to the freeform field - let freeformExtras: string[] = [] - if (configuration.freeform?.key) { - freeformExtras = [...(presentTags[configuration.freeform.key] ?? [])] - } - - for (let j = 0; j < elements.length; j++) { - const inputElement = elements[j] - if (inputElement === freeformField) { - continue - } - const val = inputElement.GetValue() - const neededTags = TagUtils.SplitKeys([val.data]) - - // if every 'neededKeys'-value is present in presentKeys, we have a match and enable the index - if (TagUtils.AllKeysAreContained(presentTags, neededTags)) { - indices.push(j) - if (freeformExtras.length > 0) { - const freeformsToRemove: string[] = - neededTags[configuration.freeform.key] ?? [] - for (const toRm of freeformsToRemove) { - const i = freeformExtras.indexOf(toRm) - if (i >= 0) { - freeformExtras.splice(i, 1) - } - } - } - } - } - if (freeformField) { - if (freeformExtras.length > 0) { - freeformField - .GetValue() - .setData(new Tag(configuration.freeform.key, freeformExtras.join(";"))) - indices.push(elements.indexOf(freeformField)) - } else { - freeformField.GetValue().setData(undefined) - } - } - - return indices - }, - elements.map((el) => el.GetValue()) - ) - - freeformField?.GetValue()?.addCallbackAndRun((value) => { - // The list of indices of the selected elements - const es = checkBoxes.GetValue() - const i = elements.length - 1 - // The actual index of the freeform-element - const index = es.data.indexOf(i) - if (value === undefined) { - // No data is set in the freeform text field; so we delete the checkmark if it is selected - if (index >= 0) { - es.data.splice(index, 1) - es.ping() - } - } else if (index < 0) { - // There is data defined in the checkmark, but the checkmark isn't checked, so we check it - // This is of course because the data changed - es.data.push(i) - es.ping() - } - }) - - return inputEl - } - - /** - * Generates a (Fixed) input element for this mapping. - * Note that the mapping might hide itself if the condition is not met anymore. - * - * Returns: [the element itself, the value to select if not selected. The contents of this UIEventSource might swap to undefined if the conditions to show the answer are unmet] - */ - private static GenerateMappingElement( - state, - tagsSource: UIEventSource, - mapping: Mapping, - ifNot?: UploadableTag[] - ): InputElement { - let tagging: UploadableTag = mapping.if - if (ifNot !== undefined) { - tagging = new And([mapping.if, ...ifNot]) - } - if (mapping.addExtraTags) { - tagging = new And([tagging, ...mapping.addExtraTags]) - } - - return new FixedInputElement( - TagRenderingQuestion.GenerateMappingContent(mapping, tagsSource, state), - tagging, - (t0, t1) => t1.shadows(t0) - ) - } - - private static GenerateMappingContent( - mapping: Mapping, - tagsSource: UIEventSource, - state: FeaturePipelineState - ): BaseUIElement { - return undefined - } - - private static GenerateFreeform( - state: FeaturePipelineState, - configuration: TagRenderingConfig, - applicableUnit: Unit, - tags: UIEventSource, - feedback: UIEventSource - ): InputElement { - const freeform = configuration.freeform - if (freeform === undefined) { - return undefined - } - - const pickString = (string: any) => { - if (string === "" || string === undefined) { - return undefined - } - if (string.length >= 255) { - return undefined - } - - const tag = new Tag(freeform.key, string) - - if (freeform.addExtraTags === undefined) { - return tag - } - return new And([tag, ...freeform.addExtraTags]) - } - - const toString = (tag) => { - if (tag instanceof And) { - for (const subtag of tag.and) { - if (subtag instanceof Tag && subtag.key === freeform.key) { - return subtag.value - } - } - - return undefined - } else if (tag instanceof Tag) { - return tag.value - } - return undefined - } - - const tagsData = tags.data - const feature = state?.allElements?.ContainingFeatures?.get(tagsData.id) - const center = feature != undefined ? GeoOperations.centerpointCoordinates(feature) : [0, 0] - const input: InputElement = ValidatedTextField.ForType( - configuration.freeform.type - )?.ConstructInputElement({ - country: () => tagsData._country, - location: [center[1], center[0]], - mapBackgroundLayer: state?.backgroundLayer, - unit: applicableUnit, - args: configuration.freeform.helperArgs, - feature, - placeholder: configuration.freeform.placeholder, - feedback, - }) - - // Add a length check - input?.GetValue().addCallbackD((v: string | undefined) => { - if (v?.length >= 255) { - feedback.setData(Translations.t.validation.tooLong.Subs({ count: v.length })) - } - }) - - return new InputElementMap( - input, - (a, b) => a === b || (a?.shadows(b) ?? false), - pickString, - toString - ) - } -} diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index e7daccd832..ef8d120de6 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -1,59 +1,55 @@ import Combine from "./Base/Combine" -import { FixedUiElement } from "./Base/FixedUiElement" +import {FixedUiElement} from "./Base/FixedUiElement" import BaseUIElement from "./BaseUIElement" import Title from "./Base/Title" import Table from "./Base/Table" -import { - RenderingSpecification, - SpecialVisualization, - SpecialVisualizationState, -} from "./SpecialVisualization" -import { HistogramViz } from "./Popup/HistogramViz" -import { MinimapViz } from "./Popup/MinimapViz" -import { ShareLinkViz } from "./Popup/ShareLinkViz" -import { UploadToOsmViz } from "./Popup/UploadToOsmViz" -import { MultiApplyViz } from "./Popup/MultiApplyViz" -import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz" -import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz" -import { ConflateButton, ImportPointButton, ImportWayButton } from "./Popup/ImportButton" +import {RenderingSpecification, SpecialVisualization, SpecialVisualizationState,} from "./SpecialVisualization" +import {HistogramViz} from "./Popup/HistogramViz" +import {MinimapViz} from "./Popup/MinimapViz" +import {ShareLinkViz} from "./Popup/ShareLinkViz" +import {UploadToOsmViz} from "./Popup/UploadToOsmViz" +import {MultiApplyViz} from "./Popup/MultiApplyViz" +import {AddNoteCommentViz} from "./Popup/AddNoteCommentViz" +import {PlantNetDetectionViz} from "./Popup/PlantNetDetectionViz" +import {ConflateButton, ImportPointButton, ImportWayButton} from "./Popup/ImportButton" import TagApplyButton from "./Popup/TagApplyButton" -import { CloseNoteButton } from "./Popup/CloseNoteButton" -import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis" -import { Store, Stores, UIEventSource } from "../Logic/UIEventSource" +import {CloseNoteButton} from "./Popup/CloseNoteButton" +import {MapillaryLinkVis} from "./Popup/MapillaryLinkVis" +import {Store, Stores, UIEventSource} from "../Logic/UIEventSource" import AllTagsPanel from "./Popup/AllTagsPanel.svelte" import AllImageProviders from "../Logic/ImageProviders/AllImageProviders" -import { ImageCarousel } from "./Image/ImageCarousel" -import { ImageUploadFlow } from "./Image/ImageUploadFlow" -import { VariableUiElement } from "./Base/VariableUIElement" -import { Utils } from "../Utils" -import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata" -import { Translation } from "./i18n/Translation" +import {ImageCarousel} from "./Image/ImageCarousel" +import {ImageUploadFlow} from "./Image/ImageUploadFlow" +import {VariableUiElement} from "./Base/VariableUIElement" +import {Utils} from "../Utils" +import Wikidata, {WikidataResponse} from "../Logic/Web/Wikidata" +import {Translation} from "./i18n/Translation" import Translations from "./i18n/Translations" import ReviewForm from "./Reviews/ReviewForm" import ReviewElement from "./Reviews/ReviewElement" import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" import LiveQueryHandler from "../Logic/Web/LiveQueryHandler" -import { SubtleButton } from "./Base/SubtleButton" +import {SubtleButton} from "./Base/SubtleButton" import Svg from "../Svg" -import { OpenIdEditor, OpenJosm } from "./BigComponents/CopyrightPanel" +import {OpenIdEditor, OpenJosm} from "./BigComponents/CopyrightPanel" import Hash from "../Logic/Web/Hash" import NoteCommentElement from "./Popup/NoteCommentElement" import ImgurUploader from "../Logic/ImageProviders/ImgurUploader" import FileSelectorButton from "./Input/FileSelectorButton" -import { LoginToggle } from "./Popup/LoginButton" +import {LoginToggle} from "./Popup/LoginButton" import Toggle from "./Input/Toggle" -import { SubstitutedTranslation } from "./SubstitutedTranslation" +import {SubstitutedTranslation} from "./SubstitutedTranslation" import List from "./Base/List" import StatisticsPanel from "./BigComponents/StatisticsPanel" import AutoApplyButton from "./Popup/AutoApplyButton" -import { LanguageElement } from "./Popup/LanguageElement" +import {LanguageElement} from "./Popup/LanguageElement" import FeatureReviews from "../Logic/Web/MangroveReviews" import Maproulette from "../Logic/Maproulette" import SvelteUIElement from "./Base/SvelteUIElement" -import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" +import {BBoxFeatureSourceForLayer} from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" import QuestionViz from "./Popup/QuestionViz" -import { Feature, Point } from "geojson" -import { GeoOperations } from "../Logic/GeoOperations" +import {Feature, Point} from "geojson" +import {GeoOperations} from "../Logic/GeoOperations" import CreateNewNote from "./Popup/CreateNewNote.svelte" import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" import UserProfile from "./BigComponents/UserProfile.svelte" @@ -61,25 +57,21 @@ import LanguagePicker from "./LanguagePicker" import Link from "./Base/Link" import LayerConfig from "../Models/ThemeConfig/LayerConfig" import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" -import EditableTagRendering from "./Popup/EditableTagRendering" -import NearbyImages, { - NearbyImageOptions, - P4CPicture, - SelectOneNearbyImage, -} from "./Popup/NearbyImages" -import { Tag } from "../Logic/Tags/Tag" +import NearbyImages, {NearbyImageOptions, P4CPicture, SelectOneNearbyImage,} from "./Popup/NearbyImages" +import {Tag} from "../Logic/Tags/Tag" import ChangeTagAction from "../Logic/Osm/Actions/ChangeTagAction" -import { And } from "../Logic/Tags/And" -import { SaveButton } from "./Popup/SaveButton" +import {And} from "../Logic/Tags/And" +import {SaveButton} from "./Popup/SaveButton" import Lazy from "./Base/Lazy" -import { CheckBox } from "./Input/Checkboxes" +import {CheckBox} from "./Input/Checkboxes" import Slider from "./Input/Slider" import DeleteWizard from "./Popup/DeleteWizard" -import { OsmId, OsmTags, WayId } from "../Models/OsmFeature" +import {OsmId, OsmTags, WayId} from "../Models/OsmFeature" import MoveWizard from "./Popup/MoveWizard" import SplitRoadWizard from "./Popup/SplitRoadWizard" -import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" +import {ExportAsGpxViz} from "./Popup/ExportAsGpxViz" import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte" +import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"; class NearbyImageVis implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -250,22 +242,22 @@ class StealViz implements SpecialVisualization { return undefined } const otherTags = state.featureProperties.getStore(featureId) + const otherFeature = state.indexedFeatures.featuresById.data.get(featureId); const elements: BaseUIElement[] = [] for (const [layer, tagRendering] of tagRenderings) { - const el = new EditableTagRendering( - otherTags, - tagRendering, - layer.units, + elements.push(new SvelteUIElement(TagRenderingEditable, { + config: tagRendering, + tags: otherTags, + selectedElement: otherFeature, state, - {} - ) - elements.push(el) + layer + })) } if (elements.length === 1) { return elements[0] } return new Combine(elements).SetClass("flex flex-col") - }) + }, [state.indexedFeatures.featuresById]) ) } @@ -638,7 +630,7 @@ export default class SpecialVisualizations { ], example: "`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height", - constr: (_, tagsSource, args, feature, layer) => { + constr: (_, tagsSource, args) => { const keys = args[0].split(";").map((k) => k.trim()) const wikiIds: Store = tagsSource.map((tags) => { const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "") diff --git a/Utils.ts b/Utils.ts index aef5d8d924..d57c4bc9f2 100644 --- a/Utils.ts +++ b/Utils.ts @@ -1,4 +1,5 @@ import colors from "./assets/colors.json" +import {HTMLElement} from "node-html-parser"; export class Utils { /** @@ -1365,7 +1366,25 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be d.setUTCMinutes(0) } - public static findParentWithScrolling(element: HTMLElement): HTMLElement { + public static scrollIntoView(element: HTMLBaseElement){ + console.log("Scrolling into view:", element) + // Is the element completely in the view? + const parentRect = Utils.findParentWithScrolling( + element + ).getBoundingClientRect() + const elementRect = element.getBoundingClientRect() + + // Check if the element is within the vertical bounds of the parent element + const topIsVisible = elementRect.top >= parentRect.top + const bottomIsVisible = elementRect.bottom <= parentRect.bottom + const inView = topIsVisible && bottomIsVisible + if (inView) { + return + } + console.log("Actually scrolling...") + element.scrollIntoView({behavior: "smooth", block: "nearest"}) + } + public static findParentWithScrolling(element: HTMLBaseElement): HTMLBaseElement { // Check if the element itself has scrolling if (element.scrollHeight > element.clientHeight) { return element @@ -1377,7 +1396,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be } // If the element has a parent, repeat the process for the parent element - return Utils.findParentWithScrolling(element.parentElement) + return Utils.findParentWithScrolling( element.parentElement) } /** From 36a0784e069733002c1cdb60d1a1913838688417 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 6 May 2023 11:05:19 +0200 Subject: [PATCH 107/257] Bump version number --- Models/Constants.ts | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Models/Constants.ts b/Models/Constants.ts index 3feecec19c..79446a81bc 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -1,9 +1,9 @@ import { Utils } from "../Utils" - +import * as meta from "../package.json" export type PriviligedLayerType = typeof Constants.priviliged_layers[number] export default class Constants { - public static vNumber = "0.30.1" + public static vNumber = meta.version public static ImgurApiKey = "7070e7167f0a25a" public static readonly mapillary_client_token_v4 = diff --git a/package.json b/package.json index e96301bfc4..c2ec8251c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.25.1", + "version": "0.30.2", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", From 5d3056cb09206205482a02a65757bf320f109eb6 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 6 May 2023 11:05:41 +0200 Subject: [PATCH 108/257] Formatting --- UI/Popup/TagRendering/Questionbox.svelte | 280 ++++++++++++----------- 1 file changed, 142 insertions(+), 138 deletions(-) diff --git a/UI/Popup/TagRendering/Questionbox.svelte b/UI/Popup/TagRendering/Questionbox.svelte index c8573ca847..8fa4e3309c 100644 --- a/UI/Popup/TagRendering/Questionbox.svelte +++ b/UI/Popup/TagRendering/Questionbox.svelte @@ -1,153 +1,157 @@ -{#if _questionsToAsk.length === 0} +
+ {#if _questionsToAsk.length === 0} - {#if skipped + answered > 0 } -
- -
- {#if answered === 0} - {#if skipped === 1} - - {:else} - + {#if skipped + answered > 0 } +
+ +
+ {#if answered === 0} + {#if skipped === 1} + + {:else} + - {/if} - {:else if answered === 1} - {#if skipped === 0} - - {:else if skipped === 1} - - {:else} - + {/if} + {:else if answered === 1} + {#if skipped === 0} + + {:else if skipped === 1} + + {:else} + - {/if} - {:else} - {#if skipped === 0} - - {:else if skipped === 1} - - {:else} - + {/if} + {:else} + {#if skipped === 0} + + {:else if skipped === 1} + + {:else} + - {/if} + {/if} + {/if} + + {#if skipped > 0 } + + {/if} + {/if} + {:else } +
+ +
+ {#each _questionsToAsk as question (question.id)} + + {/each} +
+ +
+ {skip(_firstQuestion, true)}}> + + + +
+ +
+
{/if} - - {#if skipped > 0 } - - {/if} - {/if} -{:else } -
- -
- {#each _questionsToAsk as question (question.id)} - - {/each} -
- -
- {skip(_firstQuestion, true)}}> - - - -
- -
-
-{/if} +
From 0e81b653631a97699ba07ce5d34b61bc4422a5d1 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 6 May 2023 12:35:02 +0200 Subject: [PATCH 109/257] Fix: styling issues; refactoring: remove 'SearchAndGo' --- UI/Base/ModalRight.svelte | 2 +- UI/BigComponents/Geosearch.svelte | 7 +- UI/BigComponents/PdfExportGui.ts | 25 +++--- UI/BigComponents/SearchAndGo.ts | 89 --------------------- UI/BigComponents/SelectedElementView.svelte | 2 +- UI/Popup/AllTagsPanel.svelte | 2 +- UI/ThemeViewGUI.svelte | 2 +- css/index-tailwind-output.css | 42 ++++------ index.css | 2 +- package.json | 2 +- 10 files changed, 37 insertions(+), 138 deletions(-) delete mode 100644 UI/BigComponents/SearchAndGo.ts diff --git a/UI/Base/ModalRight.svelte b/UI/Base/ModalRight.svelte index 9ba2f4ec5e..6decdfd672 100644 --- a/UI/Base/ModalRight.svelte +++ b/UI/Base/ModalRight.svelte @@ -8,7 +8,7 @@ const dispatch = createEventDispatcher<{ close }>(); -
+
dispatch("close")}> diff --git a/UI/BigComponents/Geosearch.svelte b/UI/BigComponents/Geosearch.svelte index aa639a044f..12980f5aea 100644 --- a/UI/BigComponents/Geosearch.svelte +++ b/UI/BigComponents/Geosearch.svelte @@ -43,7 +43,7 @@ } const result = await Geocoding.Search(searchContents, bounds.data); if (result.length == 0) { - feedback = Translations.t.search.nothing.txt; + feedback = Translations.t.general.search.nothing.txt; return; } const poi = result[0]; @@ -61,7 +61,7 @@ } } catch (e) { console.error(e); - feedback = Translations.t.search.error.txt; + feedback = Translations.t.general.search.error.txt; } finally { isRunning = false; } @@ -70,7 +70,7 @@
-
+ {#if isRunning} {Translations.t.general.search.searching} @@ -81,6 +81,7 @@ {:else } keypr.key === "Enter" ? performSearch() : undefined} diff --git a/UI/BigComponents/PdfExportGui.ts b/UI/BigComponents/PdfExportGui.ts index dd280e692c..95cc929b0c 100644 --- a/UI/BigComponents/PdfExportGui.ts +++ b/UI/BigComponents/PdfExportGui.ts @@ -1,21 +1,20 @@ import Combine from "../Base/Combine" -import { FlowPanelFactory, FlowStep } from "../ImportFlow/FlowStep" -import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" -import { InputElement } from "../Input/InputElement" -import { SvgToPdf, SvgToPdfOptions } from "../../Utils/svgToPdf" -import { FixedInputElement } from "../Input/FixedInputElement" -import { FixedUiElement } from "../Base/FixedUiElement" +import {FlowPanelFactory, FlowStep} from "../ImportFlow/FlowStep" +import {ImmutableStore, Store, UIEventSource} from "../../Logic/UIEventSource" +import {InputElement} from "../Input/InputElement" +import {SvgToPdf, SvgToPdfOptions} from "../../Utils/svgToPdf" +import {FixedInputElement} from "../Input/FixedInputElement" +import {FixedUiElement} from "../Base/FixedUiElement" import FileSelectorButton from "../Input/FileSelectorButton" import InputElementMap from "../Input/InputElementMap" -import { RadioButton } from "../Input/RadioButton" -import { Utils } from "../../Utils" -import { VariableUiElement } from "../Base/VariableUIElement" +import {RadioButton} from "../Input/RadioButton" +import {Utils} from "../../Utils" +import {VariableUiElement} from "../Base/VariableUIElement" import Loading from "../Base/Loading" import BaseUIElement from "../BaseUIElement" import Img from "../Base/Img" import Title from "../Base/Title" -import { CheckBox } from "../Input/Checkboxes" -import SearchAndGo from "./SearchAndGo" +import {CheckBox} from "../Input/Checkboxes" import Toggle from "../Input/Toggle" import List from "../Base/List" import LeftIndex from "../Base/LeftIndex" @@ -24,7 +23,7 @@ import Toggleable from "../Base/Toggleable" import Lazy from "../Base/Lazy" import LinkToWeblate from "../Base/LinkToWeblate" import Link from "../Base/Link" -import { AllLanguagesSelector } from "../Popup/AllLanguagesSelector" +import {AllLanguagesSelector} from "../Popup/AllLanguagesSelector" class SelectTemplate extends Combine implements FlowStep<{ title: string; pages: string[] }> { readonly IsValid: Store @@ -158,7 +157,7 @@ class SelectPdfOptions false ) const locationInput = Minimap.createMiniMap().SetClass("block w-full") - const searchField = new SearchAndGo({ leafletMap: locationInput.leafletMap }) + const searchField = undefined // new SearchAndGo({ leafletMap: locationInput.leafletMap }) const selectLocation = new Combine([ new Toggle( new Combine([new Title("Select override location"), searchField]).SetClass("flex"), diff --git a/UI/BigComponents/SearchAndGo.ts b/UI/BigComponents/SearchAndGo.ts deleted file mode 100644 index f6063c8606..0000000000 --- a/UI/BigComponents/SearchAndGo.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import { Translation } from "../i18n/Translation" -import Svg from "../../Svg" -import { TextField } from "../Input/TextField" -import { Geocoding } from "../../Logic/Osm/Geocoding" -import Translations from "../i18n/Translations" -import Hash from "../../Logic/Web/Hash" -import Combine from "../Base/Combine" -import Locale from "../i18n/Locale" -import { BBox } from "../../Logic/BBox" - -export default class SearchAndGo extends Combine { - private readonly _searchField: TextField - constructor(state: { - leafletMap: UIEventSource - selectedElement?: UIEventSource - bounds?: Store - }) { - const goButton = Svg.search_ui().SetClass("w-8 h-8 full-rounded border-black float-right") - - const placeholder = new UIEventSource(Translations.t.general.search.search) - const searchField = new TextField({ - placeholder: placeholder.map( - (tr) => tr.textFor(Locale.language.data), - [Locale.language] - ), - value: new UIEventSource(""), - inputStyle: - " background: transparent;\n" + - " border: none;\n" + - " font-size: large;\n" + - " width: 100%;\n" + - " height: 100%;\n" + - " box-sizing: border-box;\n" + - " color: var(--foreground-color);", - }) - - searchField.SetClass("relative float-left mt-0 ml-2") - searchField.SetStyle("width: calc(100% - 3em);height: 100%") - - super([searchField, goButton]) - - this.SetClass("block h-8") - this.SetStyle( - "background: var(--background-color); color: var(--foreground-color); pointer-evetns:all;" - ) - - // Triggered by 'enter' or onclick - async function runSearch() { - const searchString = searchField.GetValue().data - if (searchString === undefined || searchString === "") { - return - } - searchField.GetValue().setData("") - placeholder.setData(Translations.t.general.search.searching) - try { - const result = await Geocoding.Search(searchString, state.bounds.data) - - console.log("Search result", result) - if (result.length == 0) { - placeholder.setData(Translations.t.general.search.nothing) - return - } - - const poi = result[0] - const bb = poi.boundingbox - const bounds: [[number, number], [number, number]] = [ - [bb[0], bb[2]], - [bb[1], bb[3]], - ] - state.selectedElement?.setData(undefined) - Hash.hash.setData(poi.osm_type + "/" + poi.osm_id) - state.leafletMap.data.fitBounds(bounds) - placeholder.setData(Translations.t.general.search.search) - } catch (e) { - searchField.GetValue().setData("") - placeholder.setData(Translations.t.general.search.error) - } - } - - searchField.enterPressed.addCallback(runSearch) - this._searchField = searchField - goButton.onClick(runSearch) - } - - focus() { - this._searchField.focus() - } -} diff --git a/UI/BigComponents/SelectedElementView.svelte b/UI/BigComponents/SelectedElementView.svelte index 5a5b732fc3..3632741abc 100644 --- a/UI/BigComponents/SelectedElementView.svelte +++ b/UI/BigComponents/SelectedElementView.svelte @@ -32,7 +32,7 @@ {#if _tags._deleted === "yes"} {:else} -
+
diff --git a/UI/Popup/AllTagsPanel.svelte b/UI/Popup/AllTagsPanel.svelte index 6baa5009b0..c178563f39 100644 --- a/UI/Popup/AllTagsPanel.svelte +++ b/UI/Popup/AllTagsPanel.svelte @@ -57,7 +57,7 @@ onDestroy(allTags.addCallbackAndRunD(allTags => { _allTags = allTags; })); - const tagsTable = new Table(["Key", "Value"], _allTags).SetClass("zebra-table"); + const tagsTable = new Table(["Key", "Value"], _allTags).SetClass("zebra-table break-all");
diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 676ce6eede..7c304d1e79 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -134,7 +134,7 @@ v !== undefined && selectedLayer.data !== undefined && !selectedLayer.data.popupInFloatover,[ selectedLayer] )}> {selectedElement.setData(undefined)}}> - + diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 74a5c95b03..af9897acbd 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -1171,10 +1171,6 @@ video { flex-shrink: 0; } -.flex-grow { - flex-grow: 1; -} - .grow { flex-grow: 1; } @@ -1301,12 +1297,6 @@ video { row-gap: 0.25rem; } -.space-x-2 > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 0; - margin-right: calc(0.5rem * var(--tw-space-x-reverse)); - margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); -} - .self-end { align-self: flex-end; } @@ -1323,8 +1313,8 @@ video { overflow: hidden; } -.overflow-scroll { - overflow: scroll; +.overflow-y-auto { + overflow-y: auto; } .truncate { @@ -1374,10 +1364,6 @@ video { border-radius: 0.375rem; } -.rounded-2xl { - border-radius: 1rem; -} - .rounded-sm { border-radius: 0.125rem; } @@ -1407,8 +1393,8 @@ video { border-bottom-width: 1px; } -.border-solid { - border-style: solid; +.border-b-2 { + border-bottom-width: 2px; } .border-dashed { @@ -1600,14 +1586,14 @@ video { padding-left: 0.75rem; } -.pl-1 { - padding-left: 0.25rem; -} - .pr-0 { padding-right: 0px; } +.pl-1 { + padding-left: 0.25rem; +} + .pr-1 { padding-right: 0.25rem; } @@ -1773,6 +1759,12 @@ video { filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } +.drop-shadow-2xl { + --tw-drop-shadow: drop-shadow(0 25px 25px rgb(0 0 0 / 0.15)); + -webkit-filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + .grayscale { --tw-grayscale: grayscale(100%); -webkit-filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); @@ -2128,7 +2120,7 @@ li::marker { } .shadow { - box-shadow: 0 0 10px var(--shadow-color); + box-shadow: 0 0 20px var(--shadow-color); } .title-font span { @@ -2378,10 +2370,6 @@ input { max-width: 36rem; } - .sm\:flex-row { - flex-direction: row; - } - .sm\:items-stretch { align-items: stretch; } diff --git a/index.css b/index.css index 61ef17031c..f3fe601b07 100644 --- a/index.css +++ b/index.css @@ -344,7 +344,7 @@ li::marker { } .shadow { - box-shadow: 0 0 10px var(--shadow-color); + box-shadow: 0 0 20px var(--shadow-color); } .title-font span { diff --git a/package.json b/package.json index c2ec8251c7..564140159d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.30.2", + "version": "0.30.3", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", From a4a3b8a5add814bf1be7212302314bb112599f8c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 7 May 2023 02:26:30 +0200 Subject: [PATCH 110/257] Refactoring: small styling issues --- .../Sources/FeatureSourceMerger.ts | 3 + .../DynamicGeoJsonTileSource.ts | 2 +- UI/BigComponents/SelectedElementTitle.svelte | 57 +++++++++++++++++++ UI/BigComponents/SelectedElementView.svelte | 28 --------- .../TagRenderingMappingInput.svelte | 9 ++- UI/ThemeViewGUI.svelte | 30 ++++++++-- package.json | 2 +- 7 files changed, 94 insertions(+), 37 deletions(-) create mode 100644 UI/BigComponents/SelectedElementTitle.svelte diff --git a/Logic/FeatureSource/Sources/FeatureSourceMerger.ts b/Logic/FeatureSource/Sources/FeatureSourceMerger.ts index 87b011edd4..8b0292b7dc 100644 --- a/Logic/FeatureSource/Sources/FeatureSourceMerger.ts +++ b/Logic/FeatureSource/Sources/FeatureSourceMerger.ts @@ -30,6 +30,9 @@ export default class FeatureSourceMerger implements IndexedFeatureSource { } public addSource(source: FeatureSource) { + if(!source){ + return + } this._sources.push(source) source.features.addCallbackAndRun(() => { this.addData(this._sources.map((s) => s.features.data)) diff --git a/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts b/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts index c607d0d311..64298f7dee 100644 --- a/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts @@ -71,7 +71,7 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource { if (!isWhiteListed) { console.debug( "Not downloading tile", - ...zxy, + zxy, "as it is not on the whitelist" ) return undefined diff --git a/UI/BigComponents/SelectedElementTitle.svelte b/UI/BigComponents/SelectedElementTitle.svelte new file mode 100644 index 0000000000..03fd6c1e56 --- /dev/null +++ b/UI/BigComponents/SelectedElementTitle.svelte @@ -0,0 +1,57 @@ + + + +{#if _tags._deleted === "yes"} + +{:else} +
+
+ + +

+ +

+ +
+ {#each layer.titleIcons as titleIconConfig} + {#if (titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties({..._metatags, ..._tags}) ?? true) && titleIconConfig.IsKnown(_tags)} +
+ +
+ {/if} + {/each} +
+ +
+ state.selectedElement.setData(undefined)}/> +
+{/if} diff --git a/UI/BigComponents/SelectedElementView.svelte b/UI/BigComponents/SelectedElementView.svelte index 3632741abc..73c470da7a 100644 --- a/UI/BigComponents/SelectedElementView.svelte +++ b/UI/BigComponents/SelectedElementView.svelte @@ -3,12 +3,10 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import type {SpecialVisualizationState} from "../SpecialVisualization"; - import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte"; import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"; import {onDestroy} from "svelte"; import Translations from "../i18n/Translations"; import Tr from "../Base/Tr.svelte"; - import {XCircleIcon} from "@rgossiaux/svelte-heroicons/solid"; export let state: SpecialVisualizationState; export let layer: LayerConfig; @@ -32,30 +30,6 @@ {#if _tags._deleted === "yes"} {:else} -
-
-
- - -

- -

- -
- {#each layer.titleIcons as titleIconConfig} - {#if (titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties(_tags) ?? true) && titleIconConfig.IsKnown(_tags)} -
- -
- {/if} - {/each} -
- -
- state.selectedElement.setData(undefined)}/> -
{#each layer.tagRenderings as config (config.id)} {#if (config.condition === undefined || config.condition.matchesProperties(_tags)) && (config.metacondition === undefined || config.metacondition.matchesProperties({..._tags, ..._metatags}))} @@ -66,6 +40,4 @@ {/if} {/each}
- -
{/if} diff --git a/UI/Popup/TagRendering/TagRenderingMappingInput.svelte b/UI/Popup/TagRendering/TagRenderingMappingInput.svelte index 33182f31b6..76736f569e 100644 --- a/UI/Popup/TagRendering/TagRenderingMappingInput.svelte +++ b/UI/Popup/TagRendering/TagRenderingMappingInput.svelte @@ -30,6 +30,13 @@ export let mappingIsSelected: boolean * This is the searchterm where it might hide */ export let searchTerm: undefined | UIEventSource + +$: { + if(selectedElement !== undefined || mapping !== undefined){ + searchTerm.setData(undefined) + } +} + let matchesTerm: Store | undefined = searchTerm?.map(search => { if (!search) { return true @@ -45,7 +52,7 @@ let matchesTerm: Store | undefined = searchTerm?.map(search => { if (mapping.then.txt.toLowerCase().indexOf(search) >= 0) { return true } - const searchTerms = mapping?.searchTerms[Locale.language.data] + const searchTerms = mapping?.searchTerms?.[Locale.language.data] if (searchTerms?.some(t => t.toLowerCase().indexOf(search) >= 0)) { return true } diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 7c304d1e79..16aaade372 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -37,6 +37,7 @@ import LevelSelector from "./BigComponents/LevelSelector.svelte"; import Svg from "../Svg"; import ExtraLinkButton from "./BigComponents/ExtraLinkButton"; + import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"; export let state: ThemeViewState; let layout = state.layout; @@ -45,7 +46,7 @@ let selectedElement: UIEventSource = state.selectedElement; let selectedLayer: UIEventSource = state.selectedLayer; - const selectedViewElement = selectedElement.map(selectedElement => { + const selectedElementView = selectedElement.map(selectedElement => { // Svelte doesn't properly reload some of the legacy UI-elements // As such, we _reconstruct_ the selectedElementView every time a new feature is selected // This is a bit wasteful, but until everything is a svelte-component, this should do the trick @@ -55,7 +56,20 @@ } const tags = state.featureProperties.getStore(selectedElement.properties.id); - return new SvelteUIElement(SelectedElementView, {state, layer, selectedElement, tags}); + return new SvelteUIElement(SelectedElementView, {state, layer, selectedElement, tags}) + }, [selectedLayer]); + + const selectedElementTitle = selectedElement.map(selectedElement => { + // Svelte doesn't properly reload some of the legacy UI-elements + // As such, we _reconstruct_ the selectedElementView every time a new feature is selected + // This is a bit wasteful, but until everything is a svelte-component, this should do the trick + const layer = selectedLayer.data; + if (selectedElement === undefined || layer === undefined) { + return undefined; + } + + const tags = state.featureProperties.getStore(selectedElement.properties.id); + return new SvelteUIElement(SelectedElementTitle, {state, layer, selectedElement, tags}) }, [selectedLayer]); @@ -132,15 +146,19 @@
- v !== undefined && selectedLayer.data !== undefined && !selectedLayer.data.popupInFloatover,[ selectedLayer] )}> + v !== undefined && selectedLayer.data !== undefined && !selectedLayer.data.popupInFloatover,[ selectedLayer] )}> {selectedElement.setData(undefined)}}> - +
+ + + +
- v !== undefined && selectedLayer.data !== undefined && selectedLayer.data.popupInFloatover,[ selectedLayer] )}> + v !== undefined && selectedLayer.data !== undefined && selectedLayer.data.popupInFloatover,[ selectedLayer] )}> {selectedElement.setData(undefined)}}> - + diff --git a/package.json b/package.json index 564140159d..e4712581a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.30.3", + "version": "0.30.4", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", From 14927497bdf5415184aa8113e8cec312681aeb4f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 7 May 2023 23:19:30 +0200 Subject: [PATCH 111/257] Styling: style menu- and theme-menu tabbed interface --- UI/Base/FloatOver.svelte | 43 ++++--- UI/Base/TabbedGroup.svelte | 170 +++++++++++++++++----------- UI/BigComponents/UserProfile.svelte | 8 +- UI/ThemeViewGUI.svelte | 43 ++++--- css/index-tailwind-output.css | 38 ++----- langs/en.json | 4 + 6 files changed, 181 insertions(+), 125 deletions(-) diff --git a/UI/Base/FloatOver.svelte b/UI/Base/FloatOver.svelte index af28c54c5c..6cbb251550 100644 --- a/UI/Base/FloatOver.svelte +++ b/UI/Base/FloatOver.svelte @@ -1,20 +1,33 @@ -
-
- -
dispatch("close")}> - -
-
- -
+
+
+
+ +
+ + +
dispatch("close")}> + +
+
+
+ + + diff --git a/UI/Base/TabbedGroup.svelte b/UI/Base/TabbedGroup.svelte index 9f0d5e169b..b0aae4b009 100644 --- a/UI/Base/TabbedGroup.svelte +++ b/UI/Base/TabbedGroup.svelte @@ -1,79 +1,121 @@ -{if(e.detail >= 0){tab.setData( e.detail); }} }> - - selected ? "tab-selected" : "tab-unselected"}> -
- - Tab 0 - -
-
- selected ? "tab-selected" : "tab-unselected"}> -
- -
-
- selected ? "tab-selected" : "tab-unselected"}> -
- -
-
- selected ? "tab-selected" : "tab-unselected"}> -
- -
-
- selected ? "tab-selected" : "tab-unselected"}> -
- -
-
-
- - - -
Empty
-
-
- - - - - - - - - - - - -
-
+
+ {if(e.detail >= 0){tab.setData( e.detail); }} }> +
+ + {#if $$slots.title1} + "tab "+(selected ? "tab-selected" : "tab-unselected")}> +
+ + Tab 0 + +
+
+ {/if} + {#if $$slots.title1} + "tab "+(selected ? "tab-selected" : "tab-unselected")}> +
+ +
+
+ {/if} + {#if $$slots.title2} + "tab "+(selected ? "tab-selected" : "tab-unselected")}> +
+ +
+
+ {/if} + {#if $$slots.title3} + "tab "+(selected ? "tab-selected" : "tab-unselected")}> +
+ +
+
+ {/if} + {#if $$slots.title4} + "tab "+(selected ? "tab-selected" : "tab-unselected")}> +
+ +
+
+ {/if} +
+ +
+
+ + + +
Empty
+
+
+ + + + + + + + + + + + +
+
+
+
From 15063f9be0759368e40c44cd97d06db7feec36a7 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 8 May 2023 22:38:47 +0200 Subject: [PATCH 122/257] Refactoring: remove _img from Svg.ts --- Logic/ImageProviders/WikimediaImageProvider.ts | 2 +- Models/ThemeConfig/PointRenderingConfig.ts | 2 +- scripts/generateIncludedImages.ts | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Logic/ImageProviders/WikimediaImageProvider.ts b/Logic/ImageProviders/WikimediaImageProvider.ts index 5d6dbbdbd7..0b75982fd8 100644 --- a/Logic/ImageProviders/WikimediaImageProvider.ts +++ b/Logic/ImageProviders/WikimediaImageProvider.ts @@ -73,7 +73,7 @@ export class WikimediaImageProvider extends ImageProvider { } return new Link( - Svg.wikimedia_commons_white_img, + Svg.wikimedia_commons_white_svg(), `https://commons.wikimedia.org/wiki/${backlink}`, true ) diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts index b6b24ab65b..d32926be2d 100644 --- a/Models/ThemeConfig/PointRenderingConfig.ts +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -87,7 +87,7 @@ export default class PointRenderingConfig extends WithContextLoader { const iconPath = this.icon?.GetRenderValue({ id: "node/-1" })?.txt if (iconPath !== undefined && iconPath.startsWith(Utils.assets_path)) { const iconKey = iconPath.substr(Utils.assets_path.length) - if (Constants.defaultPinIcons.indexOf(iconKey) < 0) { + if (Svg.All[iconKey] === undefined) { throw context + ": builtin SVG asset not found: " + iconPath } } diff --git a/scripts/generateIncludedImages.ts b/scripts/generateIncludedImages.ts index 69e01a98fd..0920945f83 100644 --- a/scripts/generateIncludedImages.ts +++ b/scripts/generateIncludedImages.ts @@ -39,10 +39,8 @@ function genImages(dryrun = false) { let rawName = name module += ` public static ${name} = "${svg}"\n` - module += ` public static ${name}_img = Img.AsImageElement(Svg.${rawName})\n` module += ` public static ${name}_svg() { return new Img(Svg.${rawName}, true);}\n` - // module += ` /**@deprecated*/ public static ${name}_ui() { return new FixedUiElement(Svg.${rawName}_img);}\n\n` - if (Constants.defaultPinIcons.indexOf(name) >= 0 && !dryrun) { + if (!dryrun) { allNames.push(`"${path}": Svg.${name}`) } } @@ -52,4 +50,4 @@ function genImages(dryrun = false) { console.log("Done") } -genImages(false) +genImages() From f0637307a353975db4c506187af88d42ae0a27e1 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 8 May 2023 22:39:21 +0200 Subject: [PATCH 123/257] Refactoring: remove unnecessary copy command from build script --- scripts/build.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/build.sh b/scripts/build.sh index 024df7c58b..7bc55d5416 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -54,7 +54,6 @@ vite build $SRC_MAPS cp -r assets/layers/ dist/assets/layers/ cp -r assets/themes/ dist/assets/themes/ cp -r assets/svg/ dist/assets/svg/ -cp -r assets/templates/ dist/assets/templates/ cp -r assets/tagRenderings/ dist/assets/tagRenderings/ cp assets/*.png dist/assets/ cp assets/*.svg dist/assets/ From 6366d82e6c446a8da2f7e2d6106e8ac42325a98d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 8 May 2023 22:53:10 +0200 Subject: [PATCH 124/257] Refactoring: remove unnused 'natuurpunt'-theme, remove custom surveillance theme --- UI/MapControlButton.ts | 24 -- assets/themes/natuurpunt/bench.svg | 37 -- assets/themes/natuurpunt/birdhide.svg | 37 -- assets/themes/natuurpunt/drips.svg | 37 -- .../natuurpunt/fonts/Amaranth-Regular.otf | Bin 48732 -> 0 bytes .../natuurpunt/fonts/OpenSans-Regular.ttf | Bin 217360 -> 0 bytes .../fonts/SIL Open Font License.txt | 41 --- .../themes/natuurpunt/fonts/license_info.json | 24 -- assets/themes/natuurpunt/information.svg | 45 --- .../themes/natuurpunt/information_board.svg | 42 --- assets/themes/natuurpunt/license_info.json | 148 -------- assets/themes/natuurpunt/nature_reserve.svg | 37 -- assets/themes/natuurpunt/natuurpunt.css | 111 ------ assets/themes/natuurpunt/natuurpunt.json | 338 ------------------ assets/themes/natuurpunt/natuurpunt.png | Bin 11034 -> 0 bytes assets/themes/natuurpunt/parking.svg | 45 --- assets/themes/natuurpunt/parkingbike.svg | 76 ---- assets/themes/natuurpunt/parkingmotor.svg | 50 --- assets/themes/natuurpunt/parkingwheels.svg | 49 --- assets/themes/natuurpunt/picnic_table.svg | 63 ---- assets/themes/natuurpunt/pushchair.svg | 37 -- assets/themes/natuurpunt/toilets.svg | 41 --- assets/themes/natuurpunt/trail.svg | 53 --- assets/themes/natuurpunt/urinal.svg | 37 -- assets/themes/natuurpunt/walk_wheelchair.svg | 65 ---- assets/themes/natuurpunt/wheelchair.svg | 53 --- assets/themes/surveillance/custom_theme.css | 11 - 27 files changed, 1501 deletions(-) delete mode 100644 UI/MapControlButton.ts delete mode 100644 assets/themes/natuurpunt/bench.svg delete mode 100644 assets/themes/natuurpunt/birdhide.svg delete mode 100644 assets/themes/natuurpunt/drips.svg delete mode 100644 assets/themes/natuurpunt/fonts/Amaranth-Regular.otf delete mode 100644 assets/themes/natuurpunt/fonts/OpenSans-Regular.ttf delete mode 100644 assets/themes/natuurpunt/fonts/SIL Open Font License.txt delete mode 100644 assets/themes/natuurpunt/fonts/license_info.json delete mode 100644 assets/themes/natuurpunt/information.svg delete mode 100644 assets/themes/natuurpunt/information_board.svg delete mode 100644 assets/themes/natuurpunt/license_info.json delete mode 100644 assets/themes/natuurpunt/nature_reserve.svg delete mode 100644 assets/themes/natuurpunt/natuurpunt.css delete mode 100644 assets/themes/natuurpunt/natuurpunt.json delete mode 100644 assets/themes/natuurpunt/natuurpunt.png delete mode 100644 assets/themes/natuurpunt/parking.svg delete mode 100644 assets/themes/natuurpunt/parkingbike.svg delete mode 100644 assets/themes/natuurpunt/parkingmotor.svg delete mode 100644 assets/themes/natuurpunt/parkingwheels.svg delete mode 100644 assets/themes/natuurpunt/picnic_table.svg delete mode 100644 assets/themes/natuurpunt/pushchair.svg delete mode 100644 assets/themes/natuurpunt/toilets.svg delete mode 100644 assets/themes/natuurpunt/trail.svg delete mode 100644 assets/themes/natuurpunt/urinal.svg delete mode 100644 assets/themes/natuurpunt/walk_wheelchair.svg delete mode 100644 assets/themes/natuurpunt/wheelchair.svg delete mode 100644 assets/themes/surveillance/custom_theme.css diff --git a/UI/MapControlButton.ts b/UI/MapControlButton.ts deleted file mode 100644 index 684a8d0267..0000000000 --- a/UI/MapControlButton.ts +++ /dev/null @@ -1,24 +0,0 @@ -import BaseUIElement from "./BaseUIElement" -import Combine from "./Base/Combine" - -/** - * A button floating above the map, in a uniform style - */ -export default class MapControlButton extends Combine { - constructor( - contents: BaseUIElement, - options?: { - dontStyle?: boolean - } - ) { - super([contents]) - if (!options?.dontStyle) { - contents.SetClass("mapcontrol p-1") - this.SetClass("p-1") - } - this.SetClass( - "relative block rounded-full w-10 h-10 pointer-events-auto z-above-map subtle-background m-0.5 md:m-1" - ) - this.SetStyle("box-shadow: 0 0 10px var(--shadow-color);") - } -} diff --git a/assets/themes/natuurpunt/bench.svg b/assets/themes/natuurpunt/bench.svg deleted file mode 100644 index 6875b8c1be..0000000000 --- a/assets/themes/natuurpunt/bench.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/assets/themes/natuurpunt/birdhide.svg b/assets/themes/natuurpunt/birdhide.svg deleted file mode 100644 index 2f7c8b27ba..0000000000 --- a/assets/themes/natuurpunt/birdhide.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/assets/themes/natuurpunt/drips.svg b/assets/themes/natuurpunt/drips.svg deleted file mode 100644 index 66803ed515..0000000000 --- a/assets/themes/natuurpunt/drips.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/assets/themes/natuurpunt/fonts/Amaranth-Regular.otf b/assets/themes/natuurpunt/fonts/Amaranth-Regular.otf deleted file mode 100644 index 22b38fd9bdd9c49a76e19394598d19b1845bb3fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48732 zcmeFa30%}w_xS%Ah5;OK1O^Zo90uG_5d~9RGxuDxGR<-=_Y}+p^GPjxa>*sFY92i@GhJBD}LfkwlKuzcrtjxhPP zYQsj2d*;1wT`y}|@H3h*F`lNA&~b}c z&0?09n*Z~fR+IN_fU?hhnkz9<)wR#OS-Sp;20ylByZ~Sy>gYlYpP1UJGSTGF=K~kX1G#Q z>esLDds(+Mi|@Oy2-onju7R#`V+IZyOubLJGKbtjVL57lpPk?5Vc58Fqnp;NH(|nr zl%a~>y5of3L4$v%kY6dfQ#h{QX8!%UTs?-tL;K8(aju6mhm4yraLiy~YUJ=I2WN~O zJjgXZW6k}iX%OY1?- zL8MP1+)5kCpQ~IcW7RiFKbY7o@E}sQ(ne_m`96>`#!=ft#1GZRlUHKPm+vdRsVe=> zT&}zFNg5xmch^w&uCzTBJ;FsMG`Qd{1uo_9?wohm;v$c5H4qr5co;-k24znXm#GZ_ zm*=6pjDA1cY1B+pD@hteVsP)8SRz1aX$ zV(CZ_Czh50DiL4|{q-Z^^508e1Psl1dd#DJGyC-)J)la*xE}4Ax4f^_y{%icY3q!C z@`*viA4{#9Si5=+SJi5@l4>?-nozG!O2g#(X$=~uH_GZY;hBkV1zICaX1ia-N@4!t zHcL>2;KWaI!gPqrj37A5!dY@tyAw2!(Nmxb6?h;&)&PIOXv3Y zzcg$5j5%7~(M9jfo6nm`GiPfn-_QAK?dA`@{P5#d+M2Da*A4m0>u+n?$oC|tzh5J8 z5_0!v%$}loO{u1bP0!#b?DhNB?}RJD73*@k5?xJQZC&GBA0~z;MtZ&Ea51{3njR{b z((a0O#gVefT`4thLrwGc`P=Eg<^S!~zirjDGZ|+_o#}t3=b7GT>YToEYV@g>XptoC zds<{MC6|6oZ}~!ZJOgOm4DD%J`BAMet=&)Sj}8pbs-R;dC zU0cnm#iK1xpqIn7$FN6r@j_~A)wLRMQB|vkB~HToX+mF4Ky&J#MGf&O>SOU5XpQk> z8ev^~q5IEZqu;`tv1;O1m^C{dP(`hh7KZG?H5-0bkS3lP)}7-xP+^7_C+HvujmN-|!65TKQFnQ#3=x zTZpTo#rQ=KA3?af*4!^nYh|vb#h7q;P46I?8{ThBRkh}(0kr$ul=C$2hHF;85Ush{ zO!`>fo$-21OEjmcBH?H)$&{cy4m9`ctJU#)N2}`>tkv^-MQd*|Ywi6^JTCz`gz5bz zv-hOmp9wG4>X?3}+=hhj1u6s2N*P{{Uwg0HJXo`uZfK91Jet!lR7>)6!b5uuM;+3< zM%vcUFcliV&@86EX?0CS(0T)&DrynXS{F!zj;ekQc*jh^^c>%FN}q16su>+K_tff| zM^esw^0ol(2dvcl7xI5jx$k+4{f^;`+qD1*o9~AXWN4nErJHS}xgT2YCC_v4^_?CGosJXa#G%@@+F=00nur*_lzo6=##oc7o`r- zWywz-HELn@yql2q?|MwEEub;54ty zjv9Dxfz!Pu;3g{ESA`dP3$SPw()($l3P*X12v!o7)s2Hi&PwTI08SX74oC(c-+GrJ?;JrwAl6pH; z;f3CBkxdAB&03Vgam1UUP-b7%DBle4f|IF*RRQE+R#cf4Rc5WB%GF5aO84&PZDSQ~ zf@I8EGwL;K9aT&(h5M=!`m6Y%3J-@bGZYF96I7Z>Dm+!;h2HNN4J^bno{?IRZ8>!OLk+#2TL4PPO zAly;K_wxQoxQ|NNSC!CTy&bC34_EP-@a3=M?60`+S912(rYNdJ+Wy*fMbA7{rs#$R zYCT|~)}pAjpaT+rGbG; z0|S+;0#*A4Lc{mG6+H}ul3U>Zv`!%O+yrMTEc65-A4xMwl{r;mkxU@86)@t*k}F7a zkS2(hm2eg09Hi(BQgj9>Z481!H}R>|6{K`BNYNPtorT1RPO@s^-3b<*3{tWRf=+oW zx)-Em6{L+&;Y{z(#0Z5!w3LKrcrSw&dXM0PgyEM2LrD=>+AA1JJYZ?BVCcyP*N5g{ zC9z;dNwAVwu%aXwN^*EBJvUfU5)36j5SCU8MphTV(r1H}e1fU>1U`>gp%7JLh~hm& zwNZ%DkPy{IjF-qKMAaCgY79|r6rx%u1WhjHt@ObVYP<-RHVRQ~6hgg{SLzK>wju-x zTqP{FB1GwM2s$izMTbM6>Ki0s_ud9aDV#uQl@x`Q6or+bP-2qNw@OfW6`Y2aR)Rt| zI9*|}50#)w-io$Vf(FSY_NtQVN0n#=iJ5`sRMO_Dc+uWU+G6Srg@$6V^!-rPE}_tH zi?GN=l(hPeN6@ghE3BZyQp3DD5Ix+9gzJNGKAMn2stJobuswfOqQVLa43RO}HRZ&1M-uG+emBUKT;?67p3Tog0@29 zMeCxJHI7miAPR{U5hG(nl+wK@D3Ovy1EZkf0(hbKJhQ<#TA?zs$_0x}uB_}&Wz}kx zRiCPi#BLCuM&8PbhRV=TNVt*0>CjdgiAhWo+M+UTS_GCht*rE|GB)!XVezagBR8p0 z_n_y-sBtX@ zdL%}CfEXyb3KlOk21<&+V#i{jN8-hr#X!#qG)a8CSZ4TEa2)Zms#nHB+fBlC6qdda z3qA5y#=cl3w^-yRdBvBDg{mBIV^vN^G$a;HD**R}i&&^C0?UXKtLTYUnii|*iB*~w zt28ZEX<97Wegm4sN;s7N=^&q#u=oQGB{v6hyG2+=ABU2gL$$I)(dmHBLgK}SI8-li zC^{X`SwM`8I}X}37u*XT9jdoFpw>-TMjr>%O1$(?hoaU2wNje&3x}fC0kx8HF*L-f zaUo90GfvSQr&Z!@oEEOIopy|4q{s!w!dDzL3r@!J#6j~_aGG}txPkW_aJqLKxVg&P zf*Rwr4DV~;Oz&dwbniUy4DTzf8wEjC6;)#uYAhx!HCAD4l6a}H3KA;-*HJi`@~cpz zyiN0t085Qkl$5GaudGE$y;U@+w~DH<3N=c~8DOW9vy;5Jgk^kpk}Df5>0L_0UDSS) zaFoI_gJb<2nY&cUE+uW&!)av~qlOhc9ZT+_{2#yzy@y!IiY0FXBbo;shkO!f{cNy| z;0g5Ni(qNx1ZXG*i~pGbC3#@6$qCS-gIm(330hZ`zK1GP^e};zl`^NOu=LGDD8B^` zB5xw{yatY<_C%z29b8A@WJZxh#!(BnIVC4*t-MFUt*I*!jmic;Mwy99{}Yv^O;k1^ zQE5@4;x|!gQ6gG&f|N$>i>a}iqM;fzln}N;Pcr4%Vu!%|6l z@lvXxlTyM8X2{i5i&Uq~V!~2dbz0{ZIEpmY;Z9;?CQ==5p%S<`txz4l&Vj`@s}7wK zBXh&*Y9y*bnFXvr2B8BrC{u8#!eJ`ju5gsXu`1r7a2#4)gLWJNcB;4Ww0jMupEZ++p2*)8-h2n(q4nr zMhB~|()()SpIX7fbxqadYGSV<2#Z~<33qwmItokgt4Z&>22Q2V)I^6RM*3h)O0NV? z_r3~ljE$}dk6(jZpsO|EaSvEDt)|u%?rLhnV@=vM&qg7oK@GEMflafX(TTOIQ!qZin87e%F{#6qRyboTCjwR9Vg<$cZl4$pn z;7}DGMlJZ2Dqh+>2?ENw}Aak+!Z4C4$Au)yB$Q221FcN#sRKO{ZUkR-ly$8GJrdK5U5Yx&#@2z7p9#wxzYg`f!4sHQ*P+HU^k<9r zJ#Y~1m5hW1hvMNRBh^B%omNX$y(SrYRuFdJOC%$uncyn)1?Dg+CZ1lLj220*Itr&E zy=1HeGY4fQlF=M_D`P>jvK7h7i%X`Bej!H2wPY*?YcATo(3y-z6@XiNPl4OOMKV3> z8n_*@N=C=t0eAK80*ehv#)b%O;@c#nbwYW6G&~u8g$9`iB`ZskjP6NzruQ&-9=$Rd zdm?zDs%tUpBz2L4Tjoq?R9!XJ)I|a}2s_ZXx=KUpsyMT1Lc0_b4p%YK zqf?+wQi>0jg8U_1N8x1T!0tHor>I`c+Bz~xfzDj8(3t{-5|&<^f;{D|=xU0RAv?~h z{L!i$>9Z+{&J@*iQ;^$rVr1M&p*QL5ZprFyJt)ry%gnbPG#7wn+^L83iogyPALs2t zxQe$0SjL)qigNbHR2msy>M5z#Q$4O8^1Mil_=ok7XCAm2eAPokZh~coQV&ho3GSiN zJf_n0Wu{+GaZwM=mo(DX>e242!7`52gZ@k4#b{tXtN|V>T$F&t^3=!j2o8a}`dA(} zSlXgKeBA_#<*83wNPMgcJ5);Xi0Z>#9$0#7eW;ZZx+1aqP$+L_s$8-*QlHwTgcXbn zsp#7ca1^6wDl)kUuA{KnqEzJ>%9)M`APsF#MLq>!@$ggWL1)1&R0%EVN2#h!QyG0E zeJ_<#{GU`c`lK@ANM5nYsY(x1l^2w%S~*psJE?DEv=Df8rFcFDaC0T68?;E560y*Wew7lCZ{RCB28&R8rGm1 zZ^asq2k{te`lo(mfXaJ8tfn`4403Ml_!lS&MqDq#viw5v0xn#|!0X$v@ z3y%%p@ihL8?0qy;cBUb^T|`*gv?1ITf@LkbA>2tC86O)e-EN5P$=k-XR6{gS(o27A z2%R3VtO+zke{}Fd?{QYO#7Z5OSk{vpA-7xLR;*MvQa)!RG(;yX<3%GRCN;_!(MZwONJ*&?Iv_QQ z&)*0-zX6NSpANMWzHGh~~NCa|gnUnjw>S@TI}OcV;+;s2+@ zoz&9RyB*vEO-_eXi4h+x9o-N*#h#}tuG7(wc*4>P($NrB@l-EJM?(%0p2sXN9Su1P z78{k0H1;q~SkQsSs_hypZE399t1%KNB3{Oz#&9j+1Zr#yr-D;ey!7S9{8a*)(`OsQ z%XP5$%#BqWHC9|SR&CT6%Cm_V@3AqI7l36XY7FH8VDXI`L$ly{^w!4Ec@n%BAE^m+ z-U0_HEZ$=is4W4@Os$Es;Z2nFZlXrgCU7C~Q3@wRPZKms(u*!Nq4r|1%to6~%eP=z zRcJ=8VzBVujMn#nGgZuJxNC;xl(#czqvqsw(|+PLwLm`Oz@il`X#G>*G-d}a=!F%) z>DcWS_&-Hp@qbz%pF*&#hqRz&CD#Dt)&e<%fd54KEwn}WKP~AOH^H*f*b@46e86PJ zwfj_0xsRADgnOy@{wiM974L(#E9}B2Q$j0Jo(DHThON*;H@Fe$Taot^xH)yTVthFV zmL0BE%wHsZCiJvo1d(#4py#c$X;_ukl<5Y`OuRK|+?!1Tj=Ys}+Ck4nu&hF~qnw|?i>SS$YVnS=buM9< zHFu=scfhHX(2?>5%PgrQ(vxr_l}1)zI#QQlkw!;qlw2+0q9gN#tKhDr?1A1Ef@N&& zfyNeqW#-;P=~xfym3Xl*J=AE@Lyaaqs9j=O(2IMZGm@q?y!XI92yO>QJt+SOcriV? z2d89EBu%(SMV`GEVhk#!($Cqg5|>JO-9^uwL-^HMjxg^g@Qm!OfT{ z_kyo;;7n{qFLeG4vs~FP;6xN9JVq`Fi)0={CPiS8$z#Yw!d+FoXhPd5ggEv;57QhFj8Ivr&HH3XbWIBNM>QfNuR?BFLu(v z!q*7+DhAhrj}eq`94sw0f^x2c8$jC#A(g2fZg#Ih|0 zS5a8BF_V;^gL|M4nb`Bo;J(N$lkr7T$~c=zIp^4G5l>(=M+2#@>GMR*IB`m#Q65I(E9iF6gcoO9laF$PIe3Rj*2popKO@^aNVBu&oB@33` zFEvYUk~C>}3zOkX@`_hE8SWy$O;orU-q>WgyAEzef0+zlKY`n* zyzNx^20i`Igvm(s=P!cq!wye_k|MCI6HY_6Zf!cc@jY1PU(=Dv&)_)L)25?o^TDZ%S<~S( z8jM#C#v=naqjjcJ*JohyY-eB@u7iWgxkSMLt6tUSy>!%u-_^>PLpJ_0Nr)C@eRpTIJMm`Q6!fJHxN z(waYkWgT*+T7{cQy*CIavm!AQOQnO;D0wE5JOfUrw3%3{#bDW4m`BQkVCnVqNOK*W zj!m9V>s$uQ{Bb^QbP?>pKb)`9&nNv!!VTy<^U(^4Z-i9mlQ$3Cn9}A`%L%a9!v&O3 z432}l1xQI^#Fj2VGS|Q|jxV5uvtZdhUqA^*z+EYAff|<=sPS|GgadyfMk|vWPR%nRw?%m@{KZ#ho}G*KLSOy~^~MX_D!PU$Wn;e*4Vf=04^X<}3b< z{9p9X@xNlJVR_M#V<`$~A22iE$H1h(DS=0W5`)GCeG%lYkXYfK3Oy^lQ{fcHHxsRG ztw(~p1aA($6moCKixp!l4y(As=Cbv)EvuARX+fpEp{~%iVg6w$Vco-?2pbmmZrHN0 z9bs3(ZiPpLw+GSyVL%teM3Y@L`p>Wi0Kg3$%_t=cs=VRZ9J>#hDc+qhrE-0>N+&ghQ;%-!ls?xa1lq!p>9Cuc8W;nORhsO_y zeCw!3@k(iOVJ@H!A=2fRx-CoVFTK8&Ws%@F&K$}vi0oF$UX`3OlwDTYiI$U6oQmd+c;a%N zP)-oaZmoDA;*-f5iJE=zPIAd9K{=x!xn=iLPW;GfoM`4GzRT$z@$(T6sbn=-=DV_+ zDb~EKL}r`36CXzQEae15FT!%FPj)F~wk3PLG6R-t(A*3PMp+x*3Xg5j`u8(n!#mFVn7WoM+%<#!2^J_zX-+&N2gUHudK|pU**57BGIy7q4$)81EIF%IS@{n}pAyO4 zDwUk@?$de?I^p@XU&+}s;os;}(p#lZmG#y#{!hcdQL^->lG2hzn$nM?mofvOM(L4d zz40XDh4^i+(M>rcA|t<{St1KoWdQMMM~1D=qjaI~qH4nlq?2<-vTyRADb=p}xSann#uJ|v z6*@i8c#GLIb7s}IeYVi2orcAi)eOVxpPgSNU2y^(#ZOx zoX#;uC*SxXa+K0U2hY>iH{e><+zcBeJ;^ubm1&J(={?YRv$UPvlqw_B&C>RjQl)*R z)Iv(V#r!IqkgRRW2^F;u30-m;Moz)VZg_Lv%ZU;>_hKzg%}F&<%Q+TV6Oq#^l2SC+ zm)f5<(%QMaVPy*lqL!QF6G<2&yQCF;lpTLLYZ6BNob^H4va6zc8Yzr^;TwPRDNAQ2 z;l}g4h{oK6JCWH{I1?*-mR=>NMdXx-vD09ICRVrbJ7x7z_C!SDqPd1`Kql00$mA?C zxdx@O+blaivePW|$l9Q+Im%A5>?8j(DP)~dPIjpN&FCSgIpi#d_yp4Xt5QoYwG<#1 zPWS<`)+8%UvZ5?!Gh{D8&SfY$qfy50fwj~^h^(*R0b3~bvYJWBc?+3K$qI?gRpgw7 z%v0oyh4dvkUt!4l929&HCk@pZ3OPaHL~fixRg}o7F++c4_1b7*_BN!2;X}?t$Y}^U z3n3>VM5=NMLe)$ua`HjWJ;<7_?1M|qa?(N0Imk+`p@G@R;eDv^P?wxskTrHYZKC8r zEz*00|60TwRIuYjNrqjMmFWmpJ_Aa5cT-y~yxyX=JZfX6O-LAe7 zMB3M&^XeV#OrQHzBMr~c@MxsZ`7~0ryBLjR4Yr(C%HFEMeQ~LVURIoohKe_F58glp z)!&SB3~~nJaw&&mwOK!hTiM$eeV2WG!`8ZKML8pXhBZ6cy_dav*||4*FT0xbI%7O| z75U|$KSzT3OVnDE7?nzopkAivWxCOw&GLE855%g#r_%IuI!h~Zn>;+$xgX3 z)?Y(X(hg$jeYzpL;D&z42}UbCak79?4KFHJl_)wPaxd%Oti+I}%#w;O$fzlM)zb51 z)yRPaWRF^Qs72$XWP?s=VKfbnWd~aJpJm@!cAaIatWWw%%MdS$0qc6eof z*RW*SO55dR!JVA$Vtq^YYVA-a-j1v%8=Ovo)1GjetM+D%^BZDYWEWBN<0|wOqZ2W3 zd=Y)Pjvff-pF!`})auiNGm3lJVKwwX&PqWJ z3^f{7tr9hh-y?e`Vbpw$8o#2(v(#ve60+xMSTQ+8i#LFkFgz8h@wDpQVn;X`uXrw` zR?du4s%W3=SvDtLb}Gg0%l@QzcD@{9c_arXT>(S8L=V`b1!OiPHbQn9Wz9-<8D)1- zG|d=AWF{tF(>Ks^P1*JwXc3zxBdcgy4kdGbRN3|cNFZD76Dr&Gs}zj>DZZ=h1IjL- z>;cLSpzQz2?w{=X$)2C=_!%}YU$ui!A^UtrTgaJvX$#Sjlak3L9`*32rWe?662QNdjq2JOkWu%k&toU(~tC$`kGX9qOO^R(?{&SuKHO6Pz?UKDN+2fKOF4^Dm zBZr$5ZdK~f&~cAm7F@G zB4;}B3M8lO;>qrv?A^(po$T2CaqBA5v&6TTv8ilLM|Sq)SB#v>hgM^3Hs*S=hbQ_e zerW*G`UQG9r-;;Kh1poEI;Xs%=5WJrYRE5ESoYx5NJyOQzKNxhoj2KclU+C2b2B{8 z3zS`iT+S%FA-_m%F7m_v$Ud9wvYGgH348MItYi2?qcO{p9Xat;EKpkk>JRbeN2G8K z`U0p!_THpbWN$+F5FV0Acbs&x%D~wadcAmdvb!eZ{vR{v5es{hu|?Lo7gP4 z5tp&zvQltP+2FsQg8Q+b{U%C^B?$481Kb@kR31C?~>gv+3S*>F4^ajT`nn6cDRHi+1>i*Xb??8>(DmY zy^_5vGbI;MvN6{Z$^VSx=OTIbT<|SrcS?4p#A6b_MEbUPn37_i(L3ppveP7MGKMd> zgZ7g7+9}%W47t96I@v>#9VFR5Dw~hVn$=;XbrflxQLXy_U+wbi_=BZA;5zy)`%&V( z8h+OgrEg`$=n}2-1A6r{y8j~*{P*(xaTe@9Gq2-zIJ81`&}9G2ux;X7oQ3Nn=!1;! zvce_f`x$11vSxOXG5$xqdfD@m9WUAMlHD%Z>yn)==^y@By%4lcym9GAVt-_ZOZK^W5S=s;JxSIc;8+ZTc)%1U7WcXueY=6{P@Q?hFe{XjBkG=SRoZ^2shktBq{=eS% zKiS**cY5Rh)4#Hnu3i7j)*$}a-If2>xBkEGTYsGI^~bf{zqgnD$BaRL>{#)SE!Y2i zulV1a`~GpQ^`G+u|2W_V9r>em@*nw;e@svM_qeV6Yg~^cH}s2#5v=V<66gciF+*W#kg0iJYVIM zGt}AC`Mh(rbBlAY^ZR&P{3=%sS9e#Q%bid=!Oa?zi}|hGj*tjcWd%oWYN-y?0BQnB zKy9EdkOI^Lqi!}rhm{w44gaD?yqygSOfV}#|mq{UhVzy?GDF5bw^GKs*yMs;o})g@3}!nq4h z-~(mUm;4U(C3jI@QjYpuEf;$BP}ZFsl$3Ii3kN0qzQV)`gCAf9);nk#n@CIVHVR_-OL4%7f@0!ct^pe~RC)C1&JLOG4xlyG|} z>;T*kbObt4M`xf5<@M${if0CLm-`0g*1VU1xula@1z#n+09Zo0rNA;^IW(;z&-=u$ zrk)RYeh91~ZY|Gs#H}aY2H+!LBk4C2|1s~k@Z8G#ZP2!z_)mDh1K0_C%J) zX9icriVK5-VmQcA)cL5$M_NaTOQdG*C;_SiHGrBx5>Okc3#0(`0J&?>sQDIcR6rXQ z&_)HcQ2~_PLhd5tTgccACAW~V$oCfVbwi2B@)ojmmr`;IdE7!CA`7>oM0z(j%K(W$ zRrFBqimMLP0BQnBKy9EdkOI^L$C;TPw6>x;_`8<#E?ik^F=^NZa3YY<-Z(OBsTrKSzS4;cF zRn;$Cw6okomk7w+b8=q3Jv$v8fcpWtcdiq8I|E%v)0^igo*C$i+(0L{&CLK_0%ieo zu`}`u{3WDY3M>Owk$yF0ui?3l=X&Bc03QJ#6StMPZPfb--**5zf!)AofT6cH(Alf# z>{WF3Dmr@=u5ZBgFK~Ssu5X~DS2=NDV)fb&FavVmT_8{SZFB{~!93-6IX1!(JR^Bl z<{1sd0CL}*+-i5X=5*p+XP`Hb!5X*RVJA1$$vt&1@tn*Xb+SqdxzmIEs( zGaFb>cmwbeuo1{1ZX0!NC;SPp1K0_C3hV|x19G8nFX{FX{sK4v90U#ldB9=b9i@!p zJiq39>pQsjaV}g7w^h*$xvQ=^Py?t5BmuR7xMbt~pCtD3?sL-IEr~CQ zJIuSUcpfG1al#^rdUy1IK;$Ue9}GkQ_36>_TU6n)J*hhY_XF}PcAut;md|AuT<&5h zugNRPx0?LxfNa9$HF+y>Im8)U{8p3q^6qXeK1^Ib`4shpMT_hF3zoKo_L6bwWa+q6 z1UE%+Qv^3fXy{Ga=;p6hcqM5KODxvL7-I}eTtd6tC})jJXb%}vPNIP~eznSn3Cs9k zSeui``^Nue)HXmf%KQeO&mjJS_z5m#C3n~*0`;+`GM2Pwudf4eKOnz#E*tA_mX7r| z%V|p4n7)cKR#V0rp6hs)AHP2)ZYyyz;*}q-ePi_=-rqe&A0f^+A|4|wWAejr!@bpj z83+XAzOG|!yaLJ5*6R{o!_&-g~$CB!cU zmH~#BBmSU_z~T!SBk&sX8>6sDsTez1jGZhl^>S`8LWqATBeO`Xpw!DL#%2~{GmA^T zoMLRI&u_ei^op^W#n{YZY-Vve562kMk5Hx|%>w05eodO@cj(67^C{(yzTfjJ<(J5E z2cJ(z#1|YwTKRpP+`cEjkb8;eES`7!e=A70n*8g4^133v-X`8{C4L)a|DKPxi}ya= zk?~S=XFqYj=i}v5&QZz{{b>Wo+;t9^fj~fh^BN3@{>Z(65j^kIp#pkH0X?LE9#RCi z1K_p@Jt#sC3ebZhI2K)yGuZs1R&mbn4mlzG5-eqWMJ*&_CdLIQ0^RjiRUbyxx8zOB)Rh) zFavVWT_8`n>8=9dV4fj>jc^3dNS>8>MguW`i|;dMk*G*BoA7$#HUJ+18v&7I4)NQlXFK0N0d@d8flq-&_k>*j#5=kEC``5(Z_uuN4=#&eeN$wm;1ms>j zxz$c?uxroBgATy`fZR}5rdxSRw?>gZgS+PBPP*xU-2d|u&sjX@qVaO~TzUOkNx7@Y zvzj{A@Lb1JZk;TzUmp{{mG~Uuw?V@v(C5>uGJVS9Kae<;PL;T*k$h|5) z&$J7*igzl~nTtls&5Pyzu;rAuk}^aRB86`y+J*Or+0QUoOcTgwNbq2bSrZ>+~Jmm(t*}z6j%l<2Ub$% zD$=h3vI(yzZUgWUuo3u}_#EO5OOcJ-vXNUha?3_;*~ly#nPnrh_t1($xX(p)*~l#$ zxn(1_Y~+@W+_I5ME?QAoYAFhlPA;-UE=spW??hi7xWjH3qp&e987n(7HeEzsGCql3 zaQ`(>nYd^m25|8`0Z0VomN}#M2w){ACk#Xu) zX)h|mo_LU15%z=`jp|M1$E%A<&pzh;R^A)EDi^)Gc!zEIl<$V#8Do|)(=96PSu%Es z{*@oQE+WT^rM;^Nn1D27U zd&r5aOpEwtz~v>U#v$x^drB=or|3k!&OjI9d-D{D$-Q;60iXA`g!rYvGQgMx%XlQC zyYv+qkMhy7e6%bdEh|LJr2jA;QJ*m*&PU7g(XxEBOnjSsv@9Pj6Aupwk#-;7zW@#Z z2Z6F#v5ZHeOXArf88sfUmVspWKP`Y6kh|gnc?JO$2nX|&JL7DGBX~ygtjseShym{G zhjO2m^uxRLDG$l;|DvF{H&3G<`ew-|>4)5jfOV8R+?McuDXFYSjpNGJ~pl5fKcm4YcA>XQsc!$O4ka&o9udG}_LRXMb0TN>MQTa5*=#Y4b{Ne%$NRKIo zYi>gaj8UNQH@!sem74`DCyj4}*httIB?{4UnYRcP;*%CrYjNqQA$C#j*edUt8~xa4 z1*IPw9=fdF8y4{HnO>O%l$FQdju<|V-LQOy)jNUgPoSCQt)8q)8~wJt$Fz|+>9evL zY0O8;JSJn!*=Ny={wkjMotDg4Z#Md=tXdW+>(%NmuKCY2ya@s-VD}8JWzRxp>tgr0 z+aC&iZQ(-q<#xA3KyG&Xt+(XkI$zoQQKXxz`lR&7@?6tq$$bPFxzm!dq^0~>&kfQ`h7wTUeZ1Se#pEb_tp-E7K)#c8gQz8OTfSqMHuL z4RkN@oW;{dPdbIo2&C2STxD2Ed^V5+Y~%f2o`->>gpc#Y6Jy;>Zk3a@Ia!l4*5GvH zZegsnP`i$7Ey~Zh0zX%Xxx#n3J1zrDBe%qDlM(?xVvHRu@k(UIEwkmTQVa2N$JE~vp_T2viztca{%9{BXa;5bqdgEnFGkEBXfWP+OB}M zEBK8~?L-=5gc18GBTVsKBT2#U*vaxENx`pnay#YUIf@jN8%4y&_u0t1N09>BwBT18 zd5m<1eU$&YQGOIKRzr?69?E(^F_PuZEno)ciDv;~&1JJC!qERb(W&=6EHoouWc^DA? z={V0YJc2uC`^K(KK6QNu)!#w&NvJ*v#m3Hzu^S^kiEsBMpZdhRD(_K=_ah#Tcs1h5 z_`DR+l0qoK8wTXt1-@P2+XeJmJbm%Z#UmH*TfA!Vp2cGpo2ysVAbByP4zKaC$PoPPB3h^bB-@teA31sXyw1VHWL#K=}<>waVy&R!4;#d7% zPP&zpU*@F)^W>V|Fpd>|%^LiT-0z3CnF)6$s0HSHXnk#w)q+%0`^r zcojihB;m@$M*}fHC*nE-y@4U5d4=>#2rmVe0n16Vg821>Hvk_28;RRQ+;*Oy@O=lc z6Zn+xyNKUS_%k4vy7uwhPuv$g4*&;&L!`?Cz9#Gk-){KkzbN`okrN>PzXZH1H@a{` zR*v_gx5)dAmv+WtMsP3I?|v8qNY5#CGk{PLdHkElXVc^cd73jQ0$BGJ^4kq#@MSqWu=r)C!8Af`ttmD zPao~xllZ9r?MK?K?BmN*7Gs3qu-JPQxJl?L6#UAIFQwvfKkastbl33~e)pqN?Uel9 zpS_o{E?Qa3m1R(+qlsl7G@6h1d!KB|z4=v!P?t>$UH5)Vk5wP<2>b~?{Ekma)%yx- z>;L2j-A8J8D5W1?PNTIY5+3eqXCtZN5g)Fsf2hc;CYZUkUJj$(=S-hLX z{f>RP*YO4JaG0je=07{n<$pa7<95CU?87hQFOxg;7RjBP{5{S8c>bFIkj;PQ1^loA z!TbU{jNcjA`AvBg)*zbsn}gr_SAjkkqhn3=8`L`d=CLmSDS&(Zp|~O5cVq5RZU)`P zf7EWxE=gPN+3f+{k3m&m?nxaA6~m!-G;|7;Q=w=fH26`QnLiEwEl?rq%U^?`zlQD(lu;{hEJ|K?r}=x|jg~gBhKxfLgd+dH{}HLl z|MZjkWsVS_xs@>l-j2S=i3Wq%rB`4}-~>M8q3y}tKlvA$3s zl*yk^@!!iS*q8h_&oXM|l^>kmsWHFFa94!LG!!`r*3_>^<(H&3#gD<$T^UNh8WNO# z6{KWgR=gX%FGz7C@~lLxFV4jGvRsDL{m30oto+Yd`4dTpt2jT}O>BsiMZn)rGnq_b ztws$TGcaS^u(}To9y)&HKunFvl;jT8+|^C)8X=myrk^{>?5^eS3G<9C@!Yz{qHn5H zGAL|gTd~tH zz|omw$BoGxJ#28h@nbTHOiN3ZcWL$0?`e}cdg7SjLx+uXB|TZol~zAB)%CtCVlw_imYEMhzS(P126F z!}eMiS-a;HQg`h^Z018+Ppo}6Y<&+beOIjmR`g-*5v=H=*wNnFcu=;}-Tkcik!a&n(#+p8i1w%}8v~k>s^N#kW_8vFP z2WjIO8zwL^{FM>n8SN=;B-Z+EZ7d_zbBtt@88N14&zt6&Ue=y8%`?qs-C`R57wj9> zDo*47f2eKOKGJq;A8T8+9o%@8tF6@zXy0i^w6m;YY-Sd*LEFV|!uM!}+CHs7`%*il z$P>-L9GCFhNbq1V6(|= zvYVnzHBBj|7fc&XKbmg))%SbeZ1pf%O9)5BxT;B&c`LyFmwoP6wT@5LRJ$g^3luv0ANB z*8bMr)?>jB2TuzAYw(SbrXkBhPE~AM@pD@pTbAuP+f>^tw%00!RvOOrv8K?Tp>Ks= z4E2OH4jURaJZxKdK8RKYJAjNQHLr= zR~}pW<;rWKlcJkMw~ZbfT^Q3jCO5We?9|vLv3p~$#@=%Db-eG$iA#_BxXL3{9<4I5 z%1c!~sPcu=&so7)(OKEq+}Y9D(>dAsCP%xEIKPXJk8dCUTzpP^o~wc@%oXEGaHYB$ zx;D5zb?tVYPw18~Ghu$h@`Mi)K1%pDp)ldcgewWf3AYpd5-TKzCRR>#CRR^ul6YTY z`^4Uf{S${Ij!GPt_-x`UiR%-$BpysWl30*fRMk|qcGcEZAFev0>b$Dkt6r^Exmx{d z_g5QUZ9%nl)pl1asCKn_rRue-_pZLG`YF#8+p1|Nx@NiCf2U_o_|R(K?tXJ+(D!-Gc%>0e`i4vI6Ig&U|@nn$-0z(8^_7-4`ZZkC$(Vsd~4Oa>Otm%C67e@?GZgdKNh&)y`QdRFPku^jVEa2`036$ zQ}t?|xfVS#z~j=#T4w7ZYjkVusm|LQH|Y1o(^*b^eV}a@(I!1N9bemc4?&%w;_YIu6e8tRV z4!!D6y1$;R*M9J4PuM+=4{SC*{)Mlf*f_{~dsLu4vBVL1^wmZwH8NV)nDErA^QOe> z)e5Z3rb&5QL~NDJ2bV<7)-4&|ZM1e6_|zj~o%$XcV1h-T==ngh?zHOF$>HgnrH?$J zU;obZw$V7(Mw#TxnStIL#%}chdaC#O7>H%36&qVzbTBwCJsdw#8PTQtwr#of2 zUtS7B4XyTPI$CWX!g#~)sri9TR=q>X@JOjj$?=`Q3y1da-nQsFD_j-^-W+hKTK5OD z9v$qo>AO`X#eOs0qBlooB~znFbZx}ps$IK{?l{!BYvRxi9US-HKcd-)_~(uc$$rAB z&k4M-`@pWvZ&RQ}e>d>r=X*bqFM&4Cycwt6jV4^zou++Iac@0q?~-AWeaDV^V#v}_ zo3|`||D%t_t{fO|OP+dKzj4~MSr59Z2b%R^?pGsgx9Qxn#iqVj2gh%pxNOw0*pcHW z^l?7gblV)qkaWwOSr;BV=g|EQ96NrdcXs**@ejTC^s4o-tCqk0{?0=qW;@q>W0~D@ zLvyFiv(VEot7P!$*}60QpdMg9eDJNvC#H8udephKrRCd=msB^%@B{?fv-jv@BKsMX zZQ5#3HXz>S89C!*N!$d}=aAoWM&yBwi)P19&bQ2d`p=`sJ@>}@PWN@1Fw1iLhrmwj z`W&aidQxG;2|eyy`1Q**sq&J$VoCGJC0S3s+sjcmy@yAeICtFq3Gw$dJ%77qz`z+D zYARa3+jw!Cbji5ufqyw+@q`B0uS{F=(xSIxzuNKovPH`$&6wx3)qe4$e)*hfrPchF z)l=g@Ej{J*Co5ilHGaI+)NksrSE{4B=@y4f=4sW(PCI`@3)-qG>T z9Ui)l###}0Wasv6P+DWgY5n{O-8sQ@d6V^Zcf-i@p8Ww$c8xmbJicqmEXOm)Ewi7T zJb2vrSJyc8?Yc?PRY`XS)XkdUsbEPBu)Q<=J9jfggBCp`&34m0J#x+)5A=xjBxib3 z>pVVj{bx@7q$j|0CNN=RryKeMXK(1+oja!PoE2-+-+A$*FLNW^tT(0rcabXKk!}<) z)03L=_=Jt09hj@XJ=^I)-2*)F!+UgneBwq5{gy({1tx6jeEsCQWxJO;A6dHj=|c{^ z%f4cLd;HF+JE-7QYq${B4QqAX?(U9->S49J_gQVD5!LhwCcRSBaXn(gu??+{c_J-( zRDh?EzHsI04>rE{QOrlj{_@GJSdTSe&Xhh+b$32D{@F3lj-9e{(g%NjtIM@$PjrA@ z!!yOx%=3=LmNxs8e&&?PT_x(2r%K?+j9D*@iq|vrYnCrPzXblJvE}E057*3@yC&ZA zwC9?o13lt}=k+>>EXl0L>8=@8&je5SLhH><)a|p?i3DUcIA_^p^Ok*6z0}M-H|AjN<7}U2?y2F?_QgslTgNNW5WpyDNIe zMxNaGQ;yy!R^R2RP(Lua%|l(2hg_qcgbfHNVCSx^;cJ|GzP79z-feA~!_%*>rv4f9?3% zS+n9Zt$OXnS^B<0eeDF(9IO6;9&C0G%e9s?@dSI;dOpzCdV>GfL=V>Y=^q5xJagt+ zZBJWCXl`k>JwwLb=k;Fa<@;B;R(-dg5%BhTi)S+SbKUvdH3Dp%W}kPTmV(V+MY)5{ zdtPy$K4!I@o@;dvkMdLq80x9pEx}W%U(kzz`Wv_XEaG4&F_R|Eefe4E_>Pv8Q&;<*bsWoEmAyOOHW@8Gecq&Z)UD>%w_8*7mbXvqR`+SW zc_8w#?U{A9bC#ZaMj!aDRS!$h?e;7<*|g;9tbB*w_UzYs;L-kjo4+4#-~7b~8+ILx z)y<6$dQzPBQTwfW@NH}4oJGrKf8Z!y{qVyi={B@)eP_b}_k92Ntv!1^Jfi(rk-IJ;u(iq>g|RW-?u5~+=_Jv3S#vNt-tg%aoSJpx7v&J;Kq@8 z0Y9&LxO=Ao!&}AQ8(>>WA&b7(U!;&lUj^t*&d>++@Rr|r8pTT%JvcqmUi7}TTi5$X z^ge3UBc@$+pA&Oq)mNiab*#2!R#P>r?f7J?$$gHdtQc^QXJ$W-eN>%EyzP5e z7u^SSyXo9GD}z@_h|4;A+9mhPmrOhLD7}#$Vb&Mzv3f$&n$+?H<@eD;ddKe{vwr*o zv0aA^Y3F>%V?Hp)F|eNH*%fCWyYA3ye0%k(ZtlD_VN-mQ4+gLKJa*g0_qI9rf7f@G zWBXalThF9_l;W^0o_)#v+$EEHYSbm%x6E;~W{r!-@V{v}a=T05!W4`CaKHyE=FD9Y z@5#CSmZfc=Z7qE3LlE1;hQRbHQMfXFHl2KXtoU50z5>HKp0IV0Ziycw;V1D-5tW~m?Gx$k!KDNj8+WXkZEDQo|<{<-%(E#1wd zQv#m+TQiI9AMm>V_MGK&^XD9nS=M1ytG7n!7SHR^rvq%etl?kLE3)-qd!PPR`-eNM zwj*NbMxJ$lRv7-H9&r5TT;0-I547Lb2fP0id34FQCpNC1*w5MCqm509wcqlj>Pc@V z$M%0}La&iS7SEsR9Dbi=`fIzNI)M9d?BEaadb%fd+ND_g?Y(;70zEJ`|B){}Hc$N) zH9R$(w+972F}o!L^yc*>=Dv7NpYpw)c)_Hf z$33J$K8k$#)eXzOb$tHO;CAVw2HyMlz{M-N#Sd?68TsYm@n1RgjT&HlkXu+XG`p@*8kanFlfzJKF;I~`j-81ZO_ zr}`&)D#ni=o02?!%&fZ6?t3nHmIhqBV!5K5_kCL!t4BB9Shsn@r^fbl+FqPi=*}n% z*Z1nd_vk_PGy3e3sK_RdJ~OIg{DyliOZTsSdxs-u<@hIBr)Jd|5x=^HWzH*G-`L`? zAKtcUV7C?z4(ZmX|Kb(hh4RdOhsJ*H(A(r*yK=P8k@RKp_A?vb-M8VJ*puCIJr$g` zMOcBhXWhYw^i$kT{nIF%$hIc}7M``#*2e{SEE6oA0iODSdK-7J1q<)#bUVS)S^qH5 z_Rh33`kb?R{5jJW-KIamRKfj#yCm}MVFTWL%;D*IucwZurRTxVWE|70eshr_r*fMD zPf&;cgP$HBubW?9eo+r{tjYfK)K&4G7|)ZDN0vO&zE#Gf_un__fL>*K{A;g$vSF8l zTE2Bp`8NC$J-9lSr3^9PX)n-2E`50|zuSQ>YvOmU-~P$r*fUKICOYlk zdn^sxrMJ!Lv3GcUx4~U|H;wg}e|kg@bk@^bMDF=Gd&es0hK%ifddKz|@kH;=LpN@B z>P_?$k=;gQ3>xnoviQk$xv^VTFWc(07i?KE@`-rc!fEI9__O+)Gt7~KVAN(lu{d(l z+K->x?$CRF&G^z%f3Sz{z$2;Fq8_7c<@|f~pu?NjEq^cGW19JNJx`!x_>k9L$1{o1 zpNwobcGuyft9E{QY;`A(b6)(k7akk*h{N`oYH%bOs*lsd%=$*!+taF@r?L!M2R)c) zz13HGtp2#(x04>=X%au>p2#cjxDcFceBJb#6LpQk6PH{#6>ob{Uwz7?ugAmvxrfNFbMz3(vYTu!+aK9Yc$a-w+>V0U)l)H zH!sWi@Wdg5`#JRQ^jneZmW>(I=kbR<5!2&O&Ru%`dxuTmI`vFx<)>vX+gGosx7Dek zx%I10S8d6r=KgDUjxKOqF5Ei%j=E3&6#L!d`#sjS-7f(-mB@9= zAF|r?%hP^ppQV3(L4WZ4f(hYUaKK*Bt@bQ;y(o910Q=_io}KzAJu~oa`soz?K|TBY zZAXB8?fhp(&l~FK%zQX&>H9Cgyehu^*~m`T4J%)qwkZDgAb471w(fbCtZT_Cko;rb_UOlO&EL;<0bLr;} z^v}{a?+;&Fqz8R(-=d!{sTVmX{oS69S{?4IT5VJN^V>gI{Lwq{_GjK4v|`-s*h9-L zvqx;a9&6v?X^;>(a7J$Kfp>EDy}oe9OK-+E4{STK{UBxz59!|@(T9E+zD}Q z^O>;u-a);UreuwrFy=8tbYl3RCo?~> z{zXKzYuDNwMAUy}$G(I9G#(M@%->IaaoQ)P+TXV+)c@<0{ign4)TMP+eX8f=3G4k( z(bLi-puT66sy_{qC-jBF-n`S9P)2e_pgn33LGhy)FV>CITI^ z$a+}>I$jC%=f#FVhloH^->~YYl2MVkJV*Za)Bow}+v8%|`u|PS-ZN({&u|*sqZwz9 z+d+uPkzCSo4Y^aKD3^3ksiqK>5*0!r>U4KYg%qXSDWu4)Tq?@#>vGQH;rrP=%Q?^Q zGv)mL_`Z6XJu`c)z1QCBv)1~&FJ^N`vctq$pt$U6Tz7++^n^zq9_81eDT2ep3$ zERtG;eoJin_C9O(`QH_WTf2NCsrZ&EyelN?^a$w9B80w9-}jdr_PcH*s${Pa>EC+U zY%(-!a=EK+?Scl6oQvGnjO=Z>S}=bE3K;Y@sBXzw-Qg=X1%ZpX#E^A61GJG=!oien zTleXh3ERg-AKsoer)G(3T- zhmG~JE6wk4ot^j}`~RHVu~WD9F1I~)#F}wh=CcU;LO(=rzv3i47FUXY zj$-yMp={7hm#wvrb(k^@6y`=eY!o7TL253d*xsbL%*`F%z_Q; z#pG-#8^Su_Ts};c)G$&=Tkn^FYvK=!MqzTJzBKLg)si%9eDED^`_{D)YqXBRSMS#y zx_0Sk#)fUXbgFs?yas>VkX2SUUAl4QS&h6>auQh2cY_hMYCpZl*PiC@4L8heFzYE+ z?-YK&d++_H+HnI1Or2sjc(y}k#1ahh@Qwx4N+>+9>8uU_6MD8C^7YxK3pgPO7mQcoQ`VQQ5h z5wL0Ph5%Fb-F~kpJ+9W|Wn`Q=choaFS@d9!1V3NT^=q{1=Gh(v*D5j#)6#WCi?1%J zT&z^xCHF8Hxd)j7I|w2h@L;icvzD7%-XHI&p9XyX1wURd7iZOS6Bjd)4P^T?SOqqK zu^irGCD_MA7&!5QIC~m*-RWP5<9syCuRrTL*Ga6J#yz+IB6jFOjVcj3hrozd7=n}6 z>UZ)=NEX11jd%wm4wEGKF+}=FFRPIz3($>qV_K%Eo^I#@&@gS)Ery^G0Y>*3HMmOm zVBMfQr;3GNTVA%v?!g}d{I`YumuH8JJKm@s5y=u}3T)}?kmPkq>-U(Z?#WI{PX>pV zJyyPDZegJclKpz7pUgjOU&!*c-53ITRd5rblK)rA3>DMX!t0N3o?AJya#+ zcbp^se2-Jbdxa{B$SR7-NMtGIB8eg8Bdr9z7>`-aO;b=x`H6EUOHIowFWsuwe4Tg}(@pPh zXFZ5&B3t-Ua>dhUL$?eAo8rY53bogAaxUvuJ>k~QTes0w>*8~yT=xX}G=olKl@;#f zwanec)M<``y^W^#gXQppm?xbT%5(EeiURUokcn|yp6gSl!aINjVu3Gg$xE?eR)+5gWJ4>`Dd12yIykkS{@N2h+9ZoDMrCNT&b21Wdqo1 z)-!)V{UTkU{fCt~x477>ob89TuDJAImljYDjfmbxwWppH%WHjWD{i@sH>fxahkF;wCL+FP0m zv9imp&r->Q?n&5(w>!Q_C93(7JOq$&fr;fLiOKtw_*#HGz-C0l{F}d54Q#kJ;U#c~ zeP1&pmt^1(6UyQtV0;4%Zx|2WChA0Kkn~DWXN0kz+F8xSH28@d$AACAn2#K9K!18R zoa@7@UGdYGTmoHl$yYwPf$Pnu!*K4C;q5?P-CmV#VBP|BZLfhWscQddu$Lx+m8=ew zh0jkJXvlS=h36mnKQb=XKheCMUp(-%XnSO3_GL{;@u73quK7e}ibc;iCe1lAWv^+y z^*Dva`q}mqG=uIqgWOfT9(;0>gJz+Zznz24+5LLa)iQHajeFA;Q_PRwSw9D>SJ(k2 z>a~^dP8eRJh?&U6j9%&+uQ_7J#aHfa5Y;Eoo!wkned|P=*0YI=cL-V`u3EEuwRT3Dp4V2LS(%_a|B_1ybvZsv%es!SW!46raLDQCYVjAF+G&lix%GJ`ql&d9r;<}o=$eMr z)><}j$4;f5zpA;qzTEee__ASYt+fT0>g}H7s&#XZT<@(L{bt&<7vnkK)$5%WXqpp+ zrmIy?8?Q|pWi@~H=&=iGn?+S~M#$%un=Md|9lr@_17r%{36{`#7VC_!{eXT3RSQ$y z#8;d(#z8s9Oi+bkM_5mkfSAC;7*@`yYROZOF&Tua?>=o?CHy5qXp2JK%xIe*5445H z0#y)-1!M>@i?IX?wM3R4=%0SPp_nCv@~V8e7a}3#mNrAJfT`nO+-ROnV}E@PeFM%o zgJtbWGtHUZ!WMC=XlB(YV^;TxQ%FdgykqBWw9y<;(HEC|IZU<3=$H4eNQu5_)VFnNndV$pw&|$j?s} zS*^k&#k&Z9iC$IKF4QzhqxG`tWFoI-fdcRu9A^0&GY?1QX>Xp(tIXCNn3Wa0KeDy3 zM{LsaQW&9uWaO@M6qXKi99%pKE{O0F)dv;Z2U~GEsxZt&Celx_nCaOV_c+#Z6U}nnTG)PVPUjE#@~dvx(d5`}i#6_t-@jpK zMb^o(Wbru$gmZGx$%|O;M#}yB8r22n)6_Pt2{E_Q1(`}vxF7}5D*Xe|vx`qiV3aud zhE-{$ru2Avc9M91UX$}&4eRnv5Y%r!-?{Vr^oA2bM|J84#c4GqFEtOH%NG@kldg_B zb4yd0ky==izaeIySkS;FZgS3=uI)E{y7j7qUfGyRymbwm?q;c(e{A`wHR9vRz`IV= ztkJJ`U$Nr1oiU=vcy8OSYk}2TgM!Ew;Uw>c-Q^{(fV;wsVz=t*;^OLRx8fNy+}u!( zbAsenS(RZif7QxfA@0pe+%!@K1-yE|>wHmMta{d7*Z# zfJy=Cah7C1z5vf}Loq;>NE@#Ky(TNV{rQ%>NcuytD|4!;DX+Lw<6L29=d{F5yqzGN z{4F!@#BYHf?xDe6-k~`KII|zCBr9<~KjuQ%F=ldu7-R;UO7a5umB?Rp&mLOGknTQ(^3->M3s%?x}CfzCiQJg<`5fZ((wVh%ptoTf~u2 zNUL`dwwtXIByB6=TG-9pcM(ZZv}4a+X-F0+TZE#m z2=wtsal%K;m}tC)r7v1b*hs9KCapl_Q)IN`0A-i+38ymbDUgP&MVYiito`@1@8atJ z*Es(gp#YS%zpJ*n+B0N^YmiHT4iuYFWwk<;b+FdxR#2mf+7>2C8o_F%b-b?Fnaex2Kcz^k zj<|VbPQA9ds4-sWR?2z%Z3CGTaj)WFE5bB2_w!c;-ccWL}@n1Z%&91ms(cx%;dxi zgwopg_fN!a*9AW3wnsSx&C||XS#%4j=!yKn7LKjpx2MGIPdbpWXSW7C`Gc(-^Wp1% zE53AHdp0l3`>5_(9arGza>7>2)I)z^re3JGPBRD_dYQgEVn@{m!loj$Z@LMNxDHKe zl*0k(wE=xMA^INum{>J7)sbWrkDH#rc6|+zLSQFm2Y$dWd?mh1*Vi+Zx9*j4?OKcX2TYiE=fI5m|a(Gs`=!J+^3}R3yZ|dHm#~IIafMj`z?VMg0^`pLojQ}ebH`1qlo&^QWQ0%7j`6$ z)NUnPi26})!x6{WksR^?2j<&;IN?cK@Cbk40a_i&W%79|LdFiIVo3x_6}b@?2&;IF zMV1)G?}7+NGUyg5GD!Fa>e2L}x*TPE?PYW5zDcJayP06*6p=I4yHBU@-f0?)f$$an zVd4SA3wTcy&xm^Dgf>yg56N*~6%w$0$AT>MQU!eXUGSQ3AVkFbPr^6ET-`mVu1gW*SYl{;QqCTM!$y~Q2SD~MPSbx&p@ zmGmM3cvIDlWJnO+^9k5Y7-%RMN2<;fBw4oykvP;{>w05DeZaTZ-y$I%24%vaAQFVp zS2q~<{$Z$A*nVb^L09}f#Bgd1%#m4Ac`tJ47Abx$CB891MH}p}Z&1~Cea3zO3+$XR zo5>07Ol0wY_TYXcY)6|Z zmz{R#SiT1F-JWkNCK;k`Wd;$|jcX&q^jpQXeCX~-WI?doP9rkJut>&Zg}3+IMO*B- z&9>Nc7b48b6zOg|SQOP4Z94#?E`mfROI!ED*d(5<+o5PBprFro?b@-Ub7xQ_?n+4P z{Es$^ZYThqQFpp%uzap=ef0qAg%o$T-YvXywON?EOBa9P_la14iPZ$^D z^W(1?R~z3mer){A_&;11&XnuR4dBeVY1~}Skz2;C=DfLZE}2U~^WY_}hI`C4ac{Wy z+-I(hH{vP(BmYmnC*O-VQ4t@`x!e{fv{AIq1ui+o@Z}?BVq%cx+ zR&-bNRP<4dP}nLaD;6l66>bVIg`Yx?Ppywt>{KKvQWQrN#fnnJ6~zt3ZN+`XW5rWN zlj6PNf0af`m9hsu*}lJWkaDQfQfZ@{qO?~oP%c(3Rk|s?@QL@El-rbv$^*(YWv24D zvPfB`ysWHL-cjCH)+?V4BtN5CaGlH~ewgmsKDUX83JB^SlXrmJo3NgJhOnt3Yy?N)WX^m-$C2+$DOgT_&&+jJgl_GQp_hW< zwmI+e&0-RvnB^4)A714a?C3_O7^GKnH5qBvBV)QsKR$!ck8^X+-&_HsQ7+iSj z*El8gpn{>^ijI1x+UwCca%q`iKCH$eXF=QDX*;6&9v(Y_(8_Z1%~rw0n>cFbf>RCJ z8AMUCW%QdBf-2XU!$&MQ4_!scN7LlrV|<~lx3&H>o%kBYQy%((5NKzU3FpnH&ey*)6Vxqs&4Lwm)Y(Q@G%U0 zgFirBjlu^CENibDyE#%^tUO`Mp#}hLrD#XP- zI`Q=SlX;K5;u6kY>cN~~ICDZ~Lp2-Mjyr?FocfaQw#R;W5902gx>K*4@#x;FIBm&g zHV5V)EjEW!p|9l^6nFabG=SX1CSSz`2B$u3G~sU>be?VeO@PlFm%IBHOZuV_MGn&&Fb zn(&p5t3{ePrqs=vP+YKH<9bzt`R2qktF4Ivuqr4MeW5cFj~$E={hOikXk=Z%nWC+c z+{d+eLLFu)^Ej0d2bLwxZ*`8((cd%#NufjNR$}J1Ou+`zAqL#BhDZVE_F_Mv0{zRh zS&rBR&z(qZ&5?U2MJ%C0<<4mVQ+FtLEm{ihm^bg?!BZOB&NTeYDp?<>Y-bwWzm4MP z0k1t?UL8Ku1=$Bzi~b|IsDmwDPqz^&Fg*-$wB|;=1xwdBtx8}vFmJ+SOl5#hswPeg z)19=-JcHsf`%LJ_TJ9N=7F?DrXnQ&qGZ{m)!^SrK3zaq+43S@4Yx+%^>^0UyjHp{} zFhRyEzEs_=JfG&iKxB^Nr{ z|2X>KcU(hb^whrzbN>bFW@}kD4-{>BLGL1@X!+9oP|rM_DvO*qfV`i8)_)XjJ~Dh$ zO?Jn>0$+okNj=fi$lV9q2I@ylD0F>9-5JVT}3hz0q-W%wY}%i9CoL#E@rY&mb7%v zoT%k1T%yibRwkV-($S1~LNlIjf3AfssGz7|9Rwf~rC`B2+Md^^6W#Zuo?cDJAl@7F z{%bd69TQc#o@M2s#6?Hh!FGQuHR!z$p&MOC3a9~&d}*TXX{QPHpW$Xk5|tKI4fmjz zjvgZKvBS{Il=y~Ximb=3q!isb=xB4tScQ={`k{$*)XdRLw_G2euu(^ag7*Aw?OsBq zc*uDP?cOc*eTO{PLValXv8LvY$|{N}O4 z&tRoic?EL8gl=57V%gz%F<>^QKY2DhUrU<}zBvb~506+A6tFjOBNdJxN;`byklvTZ z<%H#*))b}`;U#uwRt*2)+*FxNXc&bDS?R?y>z#!wRm56j8_=+K$yWc3`z63Vl9c=njEFbJ5_91>}@D?IXW~Ois(A zal!C^LZ}(aK{%c$u2&S)CbZ33z>kw5`X1}4NZn77&Xl-MZDVj?8NA>Fg1z_b+^nOU z!rkV@x<}|T9P!$Qq=#h2lT1Aw$2~jqz7&4ZsPv75cE?C)n<&ujL|#v&scEnyLzWI4 zed<%5@KmG@tQ&d|Df7EN4<^t*(4v4RR+W)vUqaCpY`~S+V3EykY4d9_!N6cPA^$PM z>qVt-;mfx5FHI=w!#7bMRvAm%8ozDhRaOQc9-};U1UdTTu`EBF`EjdYI8}Ts|8z%G lDfVY19Md9yf%a+YfpgmvDvZ{1!zju+JT*xmhX3aE{{T2fzt{i( diff --git a/assets/themes/natuurpunt/fonts/OpenSans-Regular.ttf b/assets/themes/natuurpunt/fonts/OpenSans-Regular.ttf deleted file mode 100644 index db433349b7047f72f40072630c1bc110620bf09e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 217360 zcmbTf2|!d;`v-i^y?0h-UqJ+B7zac|gaHvZMg(M25z!b^#2qbHTv9U^!UgvYcQZ3G zG8@gze9f{lGcz;Wd&|uB%=YC~xO~5JXGYPt{_ppFV~0EU-gD1+&a*$ydG16gA;gS7 z0_mJHsG#pIQ%)4&yYC>xI-_q+ZXSu_pCWw5{pc0lw`9Peunc_$&T*e~?K^02wl1;f zvp9c;5dPHxgOXDp?zQU-A@nHjSB{=Ea%#d8$yJ0H4r78gqi4-<==+85B_wJs?(Z2l zb^N3UMjkN|VtI=Y#o_TItEUnxabdiBao;fh-Z|rDblqA+h2p#Mt^hlR`r;Hw% zVEOmYSV|h^8!~C+eN$z9I5nQ%g6AERM@|}B?O#?)_^|s3k)578rFsU}=f`^qZ}bup zmpFC$*r|P&MvslT5+ z!{^}n3s~nx5`%kt1@MDBlh}n6jG-hPe}a_qO5m}IUc)h;tv`f&d_RH4a5E1rhV{Yv z=K;2K`93m+dza+#*GVbvRWaPNYXWJx&QBr>q-&>13U`_~rM3J<{IZ^88pAieK-{=q z%oCE0=S$>0NfBBnv^K!KN5VV9{T)r-)FLukNOWMd2sY56heV6UmKOG1cA6xI=)h>v zx&f|QcFt(gx=FOSf-$cHe+=(`)8wC!3W*k=1EWQ#fd(Ie7LVijG}=|+6q$CD4vZG9 z8{;!}&S=rPffkI`j3#W|Z2tc`V(n~xhJ(L7G9CrZ+4|X0!ViO!;pzW4GJa+}^^ZsJ zI$IBTp5SQV8e2ZcI@bc%9i+84l4u;?kZ2$$>A|BP@?0ipz@v~6++T-h&oEvU+-Q&& z;Ovp$(HA@huGipxGKu2sElrG$Z0&yh}32N)7Ne0ERLjnH?( za#G0j99y4!6z~ciC$AurcYl{6QVJ=|y4*cxS*&>w4-MS*v~4-)S(eFC0UOu<@r2m5@9_5DR6*;*yJ1YVeJ zke;1QbZjj7Nzk@|6v`vWS=IzRBij?eR|t@*`mmeYqg&lp-M}mRzJexNIa)@U_@^I%-;t$rBkWzRxQsWC-n&>bR zAvM@|cI3_l8s8JN7hsPpWFF6frg>zGY8M&9`~%(%A7Kh?>l9MLUxCe0i*xvRG6}dE zg_a|aB-@0eBul=9tO5;ZE1{3%>BP-=6+AXh-jno2a|DkQL09Ha#LJ+=K)YgmuL8fg zWqZkN-b6IPahVShXpLkF@D?StUF2g){}I@9LZ_iEg8hp}v!HcHOF+v+^Pst~e!{%E z&=EW-xp5SHFU*l-nb2+MaE^IPfe-qZvBu_MID#Eh3$>8Lqr}AImH7rCdm=9tFJa7? zyoYR2%p_5)VV(0K7u9k%>|!a|OGwioLYlQWM&O8{o4ZRq&iI<~8u&|Thb>(NJ3Wrr zWjbP43`ITg-5Z8yHAG2A^c6^KHU06pAF$h;l zg6uD>H5qnkQDDu=ci| zb?jjB54*{ZXM_V7?=a~p%Ojr9k?ALAN{zLRuLkUn@$DU`wp+xZ^c>X*wC?ml0{Q)27AkpIh@RJ062f1YSMF#nibmKqyT7m0HAw2#6J%;f5 ze;kJc?}h(#pV17qeO~amlkrz;ALowqk$0Tm@`*z7{XdR(`ZOVZ@V|}{Ux$2~)<@8a zkQ-k@k4(c6SZBwkDl}-ao2`oOB`IhTtno=C4ZcJ*_ZvxqZchMjR|snmv;0l`23->+ zA4-NXzeDoXzJ@KkqjBJWG#T2{T=$mKf$uF(;QOy=U*miT+P?(oEAC79L+FnvH_*tU_vy|5;`3HWFe80&Lp1{kO#cU*viV!VoOVNOA8=(0OCTjo4i5ZqV1`NjsnEH=~ICC zIu|ZM{3;;+01%U#5I+crSM!hXPw*E2F%fh^iBKsl6V?ka3U5V|=w8taqjyKY7JWAQ z7eGuLW{2J3kRVQVWCkIg0f-*~#82D-@!dg)jjbRa1BeMAZfa?1xk50WI2qrP1ABFz-D^H4E*2Ny2+k8>TSOIWUzC3l zer4_#^9fD<33z$+cV4U%}|0Gpe@ZWH$H zMYPc8r_h%>j-TQDI|{ACJ`}km+8M1F4H=lwhfc-$^w8Nq9ckr1-MRg`l+nT^zMkLA zZ{fG{+xYF|dwvJ`f!|4fpOqRF%~O##Py zXc|qYU1$dFN;7FU+MV{GJ!vnRMZL5)Eue+89d*+(I+zZjchPd{;~%EO=x|y=N6<>h z%P2aUj-g}eIDQYUq7&#udJnyqPNI_$%}k}!=yY05XV95+7XJuWMR(AhbQgVy?&kOM zkJ3lzUiv6)ppS7AxQX;T`aQi$f1uaskKpB>=`ZwG`WyY7yNCWk|Kv7s8@WwfJ-3P@AHrG`}jQmagK15pTbY& zr}5MIYJLVklWWVb;?lTuZa7y#h_~&B;xlG_WN;*3Hv@+MvyB)^7 zk*-|V$o!;^j@Gz`NxI21! z13kSrds2g=2kF74a5?9< zjK5@Hd2UXm)9Fj=jTw>bt8|qEF9%>7+iG+ zHJZAqxj;85Dfd%cKei&$pSRNIH&j;9ZU9wUdR}Rf-#qZ{azE$Jb5xB4GVouP%h@&3 zX}sA71N{AMgi(Ef9AMb#WN27%)JsO;#J_N0dEneZMnxVX-sD7|pQ~hdUJTu_4rX^2 zhVI;aywU~Q77Z$|LyD$gj4KxyUoq0Za1^*}A|s5;;Me^T>2%eZjE>A?z=*yM09`O< zg2OM1^UK*&tsekSvPbIh2PDz`5jgx1i3#G2CP$_V!?1C3UAdLP|7KN%V@3xMou3$B zgtBtKHwPH=jtnwM?!nHHJpLB=aV8aRS z+&hMGl}84K0R#G#Zl$A~i{yRiXut(W9=^D;d*H8M;Z~vj&d4FLcIZo zKf#eZHYeDRo!>SnPIz~p{LpA}c8YQOw}}+dqO$Cyj!OY9kop~rlP2;aaWML*5V+$FjUeEfGH`97bj`;;2MNQd zS1t1@y(+JU({hmq0W~1Qm1FRHRg^rfp;{Vw5KjR{Ts}${9#nZF13ea^hu0T?crXsZ zsRs`&e_BKEnDiGDWwQ_1CAQoe)V!n=_Ghh94NEd{8QN zhA)%6TUE|{$6yDI9vqX;4~~hZdN|!rMf3fN;$n)6JTXOi?wGhV!(g|k-QWmwON>Hj ziIMXyF@*)5m;&50drX66lpid3@H9{Ld=~!{&-cxXi1|K`x;(Li+j=4g+dS66Myeld z@aPBY^#k-=jQ+fy)9YLGoE-LkF!hkZQ^*4H6#0<|20|CwsEi(^YY&zUN=z&|s%U|U zP?g;6r_22ALF})0;84GOnV$?EdUyFjN>}@8SFIx1QAPgLLFIl&l&{D?244(O2W=$V zS6!W$SW!J=W+MB{NUWYAeF^=MPQ&585V?ieNq_9Z*~v`V5!pFhYV{HFiG{3#mwlC8 zy!BVKuaioq zJ~6?61IcXCLg&$|+(fR1JHUO&TlwDn2>507Ai>W<8{ux@IpKyZPxgrHlsrwoPJUW` zLlLRSQH)XiOW9kwQ2DVcLN!=br#h-usx#I1syC`%R$mT_2^$i&BkV&>N6j3~8=9ZA z?X*SOHQE=nU+GM`F1i7_>ADTN$91pi+v(@%4;vJQGQ&n=d*e9cM&sKigDKv$!1SEy zx_N;45%YVN_LeHkqn0Lg&5@%c7e}6q{JE{UZEo9p+dkU%leWJ`DWjsJ=0&ZH+8K2q z>V>EaQJ+L7MrTClNBg2DM&BR3DtZ@~_hd{&Ooy26F~u>%V*&aTdUXSs8Nb8f6G))?!IO^NLtTN*n$c1CP%?B>`Om);fQN_J(r2DmC+ z4ed1T+P3T1u1C9m?S{8I-0oDn_u75c?oYSEZFa}FJG;H^f$ov+>Fyf$M)zL#GwxU2 zZ^m_tD~h{2?%ufj<5tD(iffEJ5_dZ8{kU)9{)$({N5prC?;hVb{;v3{_&M=s+Q+u{ zw9jcjxc#{Hv)eCgzoGrE_D{8cwf)=eKW+bG0!h#$*b|Bqh9^u;n3GVOP@m9{a46wK z!e1SdI`rz$zr)ZDV>`_1u%yH04xc8*Cw5KDPxK{DNSv3rGV#Zb6FScASl4k=$A>!} z>iBZUcRGI2@%JQIk})YNsZ&y~q=KZfq_IgelMZzfI$1lpJ9X)l*XgcK<2%jiRM%-o zrzbl-*XdNJ_d0#u>91sEa+~DDPfhQZJ}rGt`l9r2(tqmGqs!VZd%7I%@=BL?x_r^) z_Y6hGJsEFxm3JNA^;D)QvpBOM^X<%!x^?W9)@?+$*So#l-Oznl_kG=e=;7`$yvMPg zhMpsOKG*YX&wuy4+Ow&bwpUEADZLi{1@>Fj@HznuMBpTs_Q_u16v z;~ZDc$egEg^|?cGD{_zJS@Y8J?$3KL@AbZ}zIXS%r|XXek(ug!1F zKU82Yh%6{Bs3|yH@MB?G;rzm*MarVFMUNL9D|Q#B7uOW8DBe)~Xz`Q%++Wu}x_@$iZ~xN%WBSkRzoh^6{{H?)`+wB`&jHo}Q3JXSm@r`5fWrfR zDH&Qax1?@hyMY}CrVYGn;JkrL2ksttu{6ANN$FpMCJ%b2tYg{kvfs*@1{Vz;HF(b8 zwSylUq8`#~$mk)Pha4F4_FbuWjk@bfdB^fzXzHj=CYDKlVx}tKhNnrXWEYec+7+)&+wX)GzpGVf4Z& z3!h(jYT{*<%cjwp2OoRz(1Rx) ze7)9CJEV4F?T*?%mW)|)W@-G=MN8jVmbh%rvR9UymycWSU;blVQe9o$`np|pjde%r zUaGrT_eI?wE0inRtmwF+*NT!AqgKpVv3$kO6;G`=x#HrAFIW7zQnfO2Wzx#*l|xpJ zS~+>;f|YAmKD_ea%9mHZyYlOm|5+8bDr!~os@|)HteUuL-l~vyZnb4~-0H5Y3s(dmY7tv(8zKeM9($?i+G8)NFWi!?lh5H*VPY>Bg@%sWurmP2Mzj(;xL*y{>**{i6DH z^?T|M)t|2ap#IzX>zg&3qc(TioVB@d^WB@rZ=SPx(dG@Cw{L!O^D~>D-~8t0k6=|f zR&yh$VaIu*Al7FEUd9Q$f{^6YWDiaDBzsaio1I2y2HHu!py}BvZcg)3*^%poRl-+z zdP~a{x?Fl%M-sgjUZvs$L2sZ`!)fFLd>R|aldP;nqlsjOCmT&P)9CRSF(!5K9zM;J zYO`A8uGl!5H^FoM@_pU1yqRe^bc5i!et214wzqEO`Yi?I$E~i3wE+vLnquaR%1dSg2bP{=is~@Fuo;2P^HHk&QBsAz>Cw+j^8XyG!M+#**y`8IYwTpjLkDg}*J)8E&YYGa7OXz1^Y zuo?$w=>Q|u8ns55-OQ_HB-xYYF=ZmQ9X=e(O*9g==HO8R)$TFkJ|H&PGo>bdOHB=2 z0d{z&6{|2yEgk7yG!HK|E5#}QZZ?e+&y_7N6EBo5D-o~Lm>ltYnpnD`l%|v|DWl4! zFKKeNc!94G_b(Dl=>gUj(Xs{fuvpC60&zbr1I=q%mJ1rW2|3|7l0?RN)8mcqD7zqZ zuxMpYLLy{Fm8?^;TPxT0^YQX_x(>QxUsQ+0wwAX2eD)3&AjcxJVa3VPdQF+BY_&#d zt--%0iZ!zJOGpS1$s$)+UForL@#!|3#~2rvp4KHJ-D9=c6>;&#XikxaLl^+X`8m~+)>!*Tlit~Cqt)<9!F0uJ81vrk}GD1JDDEs zy?f&-q;^S9i@WnWbqtIo7S(}K^qFo%1TPg z$_nY(ts7Tw-L!u7L!#L9?glj_{F!^E?xQRTGPi*JpR~|PdxhQ6IZ^y z_UVXPBn6!<)5 zeSDIxvn-j9h~qnSa3q@?szRSbAX$kd91BghXM#{p}~Q%kz!&RW@o*jH(HZDyT6o(dpGZsv>S1l^QsOtBWZ;jf?l#Oq^!>`rRuw zu3Ni@4J-af?6&VXJ^Ryd^v#n`i76O$2)97cA!^f+&fZ8=TvCNrtqN1=4T73#IgLiE zhW=7wk1Fex)SJA?h{sm$w#&@WoAG9MhK%RdCDPSx#G1eM`*-_)5tl~MrHOKjICIL8 z81YyIoha5<(7c!$ca26 zTxBitsT91v$j3(n(w_($! zhQ0ONC)oX}!>;3W`T(6SJ|M0aPl}&lx28M(xy4I>8WA~n7Er56JFfvH#7Y{b5mX8V zRmri_#B>?7caX`U!kjK+T83P%h^HRz>>i~x?VWO3vr;fEo?-2@e>zRXh+|+y-O!#9 zu=)0IsxT9?jtXre4eBDFK|#ZdeQaQ+K5l6Z4D3v&y`UVJ7F5JDy=b*SH&~s5yD5t< z@=xu$`hmM28B_lHwKu=p@t*i1_tP3$b7;%jK{J>47%*+$#X~E^pWYHrBU3;LYP*C; zKoC#*c-uu1vqC|5TdY>zK7qH}?6xAG-L7`KqlvaJI!~-vyZ(vSHat+-IH_#t z_lw`XDpagI6s@!!UVq`TtK+WZ6q-QQYc?;rXKq^F)V>2>W!|`;_?v>4awl%Z+_NY&Cmbx^c7JYusg}qu#=`nWpMkqiUoFtnVEnp8C12Ab|lB^ zYGVv@!U>TZ`8c;GOc&M97pBu$c#FNrXNlmI@JL{egIva7%aojt5LqR2Y#`25yA>SA z@tz>ZxnhYdWQ^soS+<#U0L`D)yWi;V|I%nCUpsZ>Kkr--|DfNm-no9=(0X6V25uU5 z$Dc5-i4Z>)U)_K0jW5I-bnt6WKfbP^aB<%FLsg6)LDNLwQ%+*M1}a1OJQO3(6~k#F zjD{gOfD}+@Lo20GTt(9r{#RAu1nY~VS_SoKg z4;2jl*SsSio;!YHW&dZUKJ@&JhWnPRoI7%JU+;E){C#7FJ(%62ZrIq_jJ6Z8I;J!1 z#7%m8V^PqGv>A#*yhSebMsPrc3vUmNh%pZn%4EdFci|uc^VZ zrVWJDGw7~w-ui+nw~8Or&PVsIeY9|4-h@Nr803=WK&2J)q@cqM5DP+VcAa^EPiMGk zM1snWi6`T{*0#imK<5stGHYII+rs~A=~8B5ILQ{)VlE|gLo7H+tCgu#7ITKNKZ5Ae0K z8Po+nL(sLA1VxHULtYXr0SiS!Zf(d&!5GS+5?jZs&iql!`qs=FP(QN!^KZWJPJHnL zV|yRE8NYU!xw))*M(MaI?v$mYk3Uf`W%T4B(?>YP_k%$#N9MHT$&bno!!yr9nsgBo0j5 z*?Fr)vSA!*4g}81v|)x-?s5<~7ww#>f{Eh3*~~1m{Al^^sv)z&lT2>9Qs4rx(Oz+41W+s1-RiWPW9}*d4Q+ff70a&5a6HM8O5# zII|F?)<;C>)Ph0>e?X}Z*M}GS^m-l9MHKpUCSou2;ko}(xvlh*WO|$qSV#C3g3%(l z5VQwuj>~8aemjFw78e!Pt)TtHKd*^gBMm>i%m3nansV^zXa6NTa^U#l$0O94;>WN2 zo6niHWZtCabHnLFN%(5Wf{Ki>EU-WzuDI z!soQ?XxaMyOwxccSvfEUf1T+=ouERkvdJvd7W!nopeyt-DutZCn~53l9&$(y!sCm} z=y6~SakjWdyobJs+Mv0IG1r%Wo<~tpD)+5eFD{ZD5toRM(P$cbV=A0ZtQqt2e_`G* z=CjDvYAO&VMLtHZD)7O4ah$Bc$MF;rPHzElr_aKGKujVv{;#GBd~)+VuA+GlS1UWR zSxl&J{;JhXDw67LgIIy`O3JIl?wE+V{y`nWm@(u`Vs*h8Xmw*~cnseB?dBlmWIZK4 zg;iLT5gezBR0?gQNMr##FPTPTEbwUrnZ3X#yG6u1S3#Y~j4&|{(NPj7p+M)-H>YuO@twzZg_>@YSTF%2qmC_&x99l`Cq=nex;govt*CUH* zY!VwAd9IQ3H+5*v#T_z_*w0klP~sp!(T-{|p;FNzBy*IDkHhCr_T#DyUD3ExUk z!`?BR$ha^y!waXBsaoUYmg@yTT~r~V1Byxb_O<5kw>CC%o6j^K=1whZeiVdmVgbH9A_9OLWMu}f1TRzfPV1RQ#<17F*cNzJ4nT<++#0S##u8pK5_T3V zRU3IZA`2ZshA+#*vXWrZkTnjN4JUQktSpQGgdQ9bMo_*)G$?gDDWT_;^rG0PQ;Hgy zVcR=R6|y5Y3I)Wr{DD0uuKyC`7M5u-kWOB!3Wk#E^-$zSQy8z%D|xC2ams(q>k3Yc zY2&yIa7)%pO_!C1oiFlHN>0Z;B%J-=aMMXl*e#N}v-rMD^FZs#PW)XoUEos*yuQH4 z-x8UdJ_o8Qp?0AB@V9j|EjuCZ6klOV4|Rw+h?Ym)sBZrG*T4S!<=19J)eno4AtP0& zOk6Hb6?cp8i|0`7*a4Asig+%d1qH@mDo`lR+eQt%1JA^4QG z-fsdH^ze1kya>8&;1^yE9l~oB+K2+5R#2FsJ`k2?y?Qe|x+y@g1;{tS^eFxwE1$nf zhy5vD$@oXid@Zib7VBvt_doG-9{AC~YtHw&wUlxil~ECn=&Cc~F7f=ghxa}4{3l#x z^KRLZH_wZoj%%L6RjyyNX5B*YEQ>@qXyA4Xd(mb%^WkQh;EvNo^EMl_uub4$QChJQ z0ntM0hb0RHmm?JNBFKoBA}Pt5!i{19rQK!|IPsgG#HN))->GWc-*e6SH=YtNeDrMk zGfynt(zSN^S5MN~lOOci8`p7Bb@9m3w1U?je*CqPjm0y@7mpm6Qi_BP1y>PaDDbC1 z*2Y9o7{c>Pq>KdU1c7G;uC-cZnucZBtWIu4qnIM(iz%|0&62%22APS#I7Z_38Vyvf zK)S|cRPurr0|mt;kTDCP*uo@5Qiq7IpciEk;@je1_;;!CwU?fIO?ITI|8?S*E* zH03?}%BiFLQNW9j0F8#MMjLM!%yJmw34zDUQCVy=MAACIod5$eb`R-I0!3OB+us3bP=upJga%(R)L zXF&*PAB=8hqX#E3dt|5fi62_isI9a3`95*p;jcT}BGak-Fg15}n$y2J%wO#Ns^!6* zO&%<3&WUZ$KE=;zTCn}))o<{%j0!MKLOtLJBQCE=kjtR*Q3(*n9ugRU z0CVF%q5dTNo2iHCS7P_$sA-=3jM$y4X`atDamSjU=lV1kv+)_$=3#s#ad`E}Q_B>* z3RZaMpq)U#9;rqpiW8BBw$4!fx&v427QjAuL(U>+?vAi5y z76nlVR8WV>1gHa^DsS_QZaFN-%Sky2r;ycT=9SF`{)MK$_~=?;%huW(6EJQA^4L=_ zt^!c%PiMqS)F~d9Ute|I$yUpfJfC13o|vBn z`tHb|nt{7xoU5VvvtarZPk6-Rv?(+*Ucf;1RH-70c*wz>GutsU^2|;Tro4oyGmgkE zt@;o1-tZoJyx@|ZgnAD5s<3~k#5-nq9VzLkA_%Q9oENkvF>98xvZ1U%Xp^WoMU#~k>85SKK z5n)pD@Y%Sq=)OKK42cMvq8Z^83ghVf1d_(hN41)J44F92J@L#qZ-vG*XuP=yoPr;wM1hz8P!G*4*<(; z#FFQO$8ZZ~@OOBt9g!d>rJAI)cU-(n!8}Tf!qderDcFGC=MH@EEn zobg6u{kF!f_4UF~@k8+!{Cjsm=_>4IELkvi?~0ed{`&N#3-7(nxS<*tUV*p!Yy`2B z9@a<2huh^^KxQB&K*qKNTBWX2I?IBw6WgDRb2*GGX@)SG8+Q$fK-KR9b|rZ-7N?4b z9@)Ko?PLqycF<5W?HFrcWs4Gry#7J=4Vl0XW)(7k9~1DC?R7@U?L?B>D(^l@dA4Uc29gf@Z*wbDP`9gHMgm%zkL74pWeGP zTdNQnmWsO?w{CA-zkS<=$M7iJh)TG^;~JeD<;^Lpk2kEKb>&$wZe^YPX5dNTXsIis1}7F zK_`#F9t^TO7LCTtVgFyoTWxBHf*J-=A^?B;0VGH|R49v0T-Ze9^GN)oFwezQ&GWpX*J$llOuG*OSb?`lT5-(bW6kw(U z$soPSWN)v;h^}r;Oix*gnVMx>huF3;?g({upSJpReR`fx*}bn%9jol1SL&6v4js6@ zz7G2KS$%!&4Yp2weYVITkA53U@skfVG$_?6&&)V;hC8+7!Q<~W zzYOV47~FNh-7mb=oX$MsJz$h^c&DF-Z|w3KS$>&PgHfx<_h~sjWt{P*6tM=Av~ZeG zg(6K6C_@?2&~UhGhxXmNweQeX$fpUJ>0P>Z&B(ymE$c*!G`0r${El9mIV?=8R7SM1 z8S;HLuS!qAeehZ&&C%wzNzAPROhfD05=V5;?bE;D){LShVyR{DT|(0hgLqsqJT!R# zr%}MEfpmetuT!hT!jy7BrWA}Oc&`S7QpqdAth_$pF(iZI*`_tz27HsyN+pj71}+ed zS`@S_v7C-NCFanN&xrHq@7=59QhJW2v&E$56`cHDah%-JbUFjSvcc#{hhT1=V3|-; za8ihbNoMpJZ!#oOAP#+`-tx1M5*Nwyx~xW{3FLRFOfJ5iyRFS?HAM*82x!`v2!mOV z$cga*7$La11tEZ_hCJ;6=eJ^rTbyC{U^~ts{bk%CcTb(QePhGa**n%XER9qqKQWQg z0m_tPvyVN;ovEog^jz}}cKp}7%_oKY`jVl?hKzaPZN@P{ZUwv+lHv^~7RIg?mCdSA z84O@ngF&tTCuY5!S8u|?ICcMS28QS8v{zqaoOrUKvT+-cs{Hd2JoYGl`X9$25>a@C1;+tTt2iOi#hPfWkk(jI^dk(}3L&i&mvlt2yLA^je~}N}3_)?U*uKSCi0?5n|eoA&=Wz(9NEcR{MOzaudeBR*8;|vmaE)8{APho%1u}-s7x{OLbzRRHkkYR zbY6pA0YWK)glco{w&Wf*oR$FZvt=6ElphgB#Z3|E95mBX)%QQp@!w*D$g@BUOO%1H)p~Cy~}xT9NjQ*$cYR1 zNfOM=VmS^ohat*PQ?&+LcX)e&P2~y2zsRy7JoR)jnGqxI7Ap^3Ezv2%X1;Mqti+(R zzQ{?Z{kYCISUinbN$$dEZDDOJs>rBlyG^G>)GjF7m|$*{Om#we2BKhA5)h1pvHgKU z0JarkGBKXYFbAgWf+>aMGv+j9`{?s8itiqnI7O)pOEH}}{7P4gQgFSnU%bH8bieS_ zh~@>zAB+}DiZY)`=Vmsq*gvyfJ@;<_1*qb&My0gISg%ompY5Tj0a78f46_PYECY!_ z6bOfdkuZy^T=b||^E`D@$G^lHy6(7mPJI11m%`b_VRUcvx6SA2aWMzubC7pA#<#Mp z2bRg(>;e)+aLcukN?7%*)SF%d3%FaY4LlPtv>6%Pp`QkrmD)jH9TF4r{_Z-p<%{GfDfL>l3qSmjnkKUSNSemry9iMJlOZ}E%|j(m0Ll4jg6ZY9^ajV4$5 zR&m2BY3la+)eN8lQ^Fp>8c{W7cNUVS%L$;fxeCf4S2$TM70?he< znN$&v_tYp|YnlSx76j`Cx zj4Kfm_%cXAJFk(~hewz+B|hGy#}7J{_~axxkr={XNq!};{Q$=v_9mVAaY((v=&(Ib zn5DQlTAIF~%b2w}(|p;ZlDjPIGH!ML1NlWmxifvbY@XCMu5F|@vwpJE;lK;`*yk5l zAa<{Srz6!eqmUU9nce{Y&`7n+1|C}n0rtDCmKjXwGFzmo3I@W*tdx09j~-c>o;+^< zjZ3oPrG33w`ChE*1oZdE(%w%mZ?sLR<&m|8`z9#)wowr>&aBqrwL7g4rVvp55UMc+ zW889zLR=yh&@y+x&FW@ZV9J6SDKO>FPS{X;_9R`ov}kooO6{cmdmegh)#{(R$X|QY zL55DLHnS``MiU+p-ruK+ zh(L*#q1a~*Co&WW-Cl5VTWL~&i*H#rsBg9libFaw4JfGsLvxKM8hdVAGjBd^5Qp|I z}9+f1N^c57yNFfx06Yy2n#c4P}8O2H5Q#!VGmd9bPBy3^<2bk z)th6?oZhTAYp7MOVU$w_zmLVW(grN2Y~T=)!|lVy2@3uL zYJou#Pz{)wWoxA{OwtfcM>PE7SKYeYmesvBMM zURhSYdzZFJa;M4}-`D4~stkR7DyW^H5+zU{w>$afP!!7~nB`a`UWP0))(Mm>-Evyu z;I)8?c02Pe{?Y|CL*{oLoA=UNpS-YeR=0bbHorzI zUT5tkanD=l#XT=iI6#y3AD|tcIv~F9KOnyMl;AqBZQq`x`z>vM{@}tNJ!W~tqtY|t zp4U%_4R4*NLtlMTy!hk+n&1yU#^gMYw{X*Bry)x*1iQm_d8C?B8}n-&&bDf`DZ+*V z0-ocwrWh>so#C%Qd?eYwX-2`eOxUH&2t0ikN)jdf8{H^%k#e1!C4AV*5mUB3I&JEFi(`t zNom%bVoV(LzL_(bP3C{(Fh+n|I*YA4pgg4D&*j345DK%4m$o|bD#ZU_HtyoRB_oFn zpGXf4?ssk9`K24FtYQ0&OaGJIxa)(wMZK4m%!?Lh(oy0re%@m7)c;~Q+HzeEe^b5z z68HCceL;TXH@qNYSpW`Lzz^fDK_*$;?)2)k(0ZulZevitXycjSwRxlUn@G@U0kLPy z*xKqWcxLh9Bc7DO@493idjj<+v1PFqjVI_(&iaV(io*X@Hyh6}*93i94&Vu{rJJ zRyFUv>MM1YWTlPD&92$<;0E7@1N10YSoPJAk;Pqda^q6Vr!1aYvbpY2%<1GZr8!;5 zzQ*cN-^!b!)$(?3({S@7GgoY;Vdh9PXErO_IAgR*WECVegcqQOhd2X}v{vSj#WdG{ zS6Fk^r8)ki`?k#3Fz@2mGiQ$KLph`r!w1oI(u zi1@@q4a?f7r+isou2wfR(D~x^=iiaS#>a-0?G|5@v)QMKO+qESbUlg39-|C_q%4d# z7*T7(>t(2f3%pJisLTw?7853yQBre;E*_^)IsM)0US%Jg{pcGmNo zP`aj8ZtJqN4>oW&a((U|YD*eX32DuSB{>00!mPF1Yho|CVf!xvAtkdPRu!`!uMBT3 zvEa{;RkX=kxry9~C+gQfzHjrEN1MgFt0oK^HeviQVancTk3IazGe`E!#b@5ES(vc| z7Ght}LO?RZRM=wV6`Wcn|2z8tB%ziBKbs{B9Qb|WzL_*eygZYZi!chI@0>=Q&=b!XcGs&j8FyFgO6%{mZ+Y_%PDX$)64d)Q%@x)c z{yyvbIr@?re1G&+9O4YDE9==9@4Wr&!f zNPAY(t+YhDXj^?-mqkeEK%%gt6%~cI`y2y&aRy^pfzRl=iT)b53#VYZU85Gsft?k(ANS~IMXem)X z%^75IBr*MOddwoVfga)i(1R8cSD;7K?LCr1v*51qw_~_NJ;+3ofgb9^Jl9SdJQR+&X*mZJ#BfN~KvDm@HpgPP*! z`At-Js|X+vVd57-SbZIweO4XDVh*IXv5$@v5(_w_#x~C6i<(W%;uSx4j6c(SoQrC{ z!sXm3qbFubWwpWLN%}VT4CA8t(5R?S1c?VT5W! zIFqV8TlJWQU;Sm2q1J!sL5o^$1bVc&y$8c;V3vu*Bw>}K&YM{60e5qVG*8C>B;wO| zK*H)2@zOj-3G|rNGV*pv7?*Gl-|9h !}#gv~NV!5|5YF|kE)J0y^zWbk4>=%|6F zy33ntw4%IFi~mIi5@F#H5DC=t8uf}S#Z!v&ic1RE28BXUsSJfa6)#wCtF~p^u#l?O z0eO(1tOyP?MELD=Km)RBA<)+2kmXB7xbwDcqlf4~djB)cr@zKqO|>VuQGqgCZaIE3 zPh2kU<-E;J^`bgJLs^!BadisA9M-epj#W!_dJhcpZBZu{FY81@5jOeF832a~R(03X2W)KY_>5w^fiM0iyS zq%u`hqg9fKkhPICljOuxNnP{%E5+Tkq7r3hd&klWarYQHQrI#Yr@Kef5#qz6X(g>3 zEAC`b-29f8QK|O_V%~l=vM$gI3^C z!Y60tXHxOrtB^`*qqJ4fEET*nk_K`bthO);LX)F!<(Xs2C;+<8w5srY( zu0@%q3gV+xX;sLVOLdx3Du!*r2e;hAbS6iRqa`(@O?lx=}0~I`prdz`0bPBzJ-?Iar*W^g&H3>}H%XNc%hQ z&qCOO`)I~fh@bt9jkl#Mb;>-SMTZT&V37&SK;U1z`MA2^}p#@GHK-TN4KYLihx- z&=`-y&Zf5NF{{N9=%EevXn7hv2H)xdTaB_JHijwG<0^W@NN@yZnJ7Ms9!%pz1R#Mv z!LKR^qfpz&-ZCrnCOYMswrx>A9AVQL%?7zDzP&0Y&lkqqj1f9Ld@vPnw@|*_%`I7$ z?M;UE{_ocr@fs~jPs8TEJtHn&hD3FIhD}Oen|LPAfn7=L_22mOQ@pUF`1j{yl$qzm zp9{VnR*}17+_mEKTOQqac!&7ZQ+u9znDdBVi*Hly=U-9z9O1new%=RZD`jRuQQbYW z*ND@_Z#FcFTOND%45O_d`Y}h6Hei&>X(>_-z)5rnuZ*@>FKGY&F!mmRQB~Rh_`9!6 z?=zW6pG-m$LI@!VA%svuZ!xrpgeD*$9T5Qm5fL#WAkvF~fDj=@mPMq=x*}MRMMQKJ z(M49#wPRTq$;`|DbMBj&B&grtpBR(Oyt(zwEic!yR+`hQ9v-nH+7mQ$5{Rw%jTou0W`yqqZpXl+JKp{;o`#6MGgfDxC6hJr~ zMf?5vWlsUlxa`9Y44%csDMRt_OJ1k6;g^0}9tpDLo{D=%Ek-cNmisDZk69G_TOqs9 z?_Pw1Y%EZ7d(C`ipB5L=V|MwHO-S%SXh_-IvZb4Tdv1dGXyHHK+dVF{u;4OL2KS@$ zogb?0{Ao@Z-pJ0~`u?1m{QW59-10u_=i=|DHTgW>S*`ua0qv}{;13WV=e}S*f)RWK zbF>!x`~jRr9>oH?iC0J!I+glUbO|1Z0}++Y(p-Ww!QwSa#$?1(dLe;)|YRv10P(7%!bcbo6Tf!QQ|Gx(fNYeC=T5r zoHeeKvfIL%kElsAXhXXj$KnZo_p;mm%TJ4TvEhB*g1#u)Lb;I5lY z|D3mw9@!N^?W#DH6Iu(G75pAPLrLEu=@rd`k4W=3Z9@b=#lR~0890MjiO9AskM>XcoaJu=E2HvNuY%&r-P)a4CO0<7zy-ICJf~* zh*4_*=AkUsc`%Az^n}<>vS1Xjy`;f6#%(WQO%N9QHF!sh6uSrtj~6n9aivA+I+smI zPL7H5#yNCy^Q`!oSYS}~s*JHa{mxt`iVHZMMmG*M{MM;Fk~pE^=FxNr4(Jn65o5zq zf~50ndViCs;*3J>X)K5-h=Kh3r_se`wUoV5y>;s!h8Pd-vvl#%ql@d(8={OsHC)H% zl+N{YP&(=7Sj{M%(!JwSh|;-Cz;1&~fO{p%U3d$e(zO=K1&7vxrO|$&1)_AVh4MPy zLPCV@v=(SLv=;gUZ@!Sm5VbRCV<4)b7KmzK5ui~-C7Uy9SIS7$+Tf>RmL@T&V`M~8 zYoFw=Pe5GM@2nr7k?^Q}O-9YBOdEBXHtMK!Ou{ieC-pPw7^$RYoYvY-a^-=Ezo(>TIes1(^`n0SUv`R z-lzpyIX_=Hsb91WwS=-@wKm|}C}ub{SfN`+FSh60Lh2d$9Gx;hpFh+JwA@eFp!FTK z{cwp!UxSumALUQ@*)rN!QYEt~!vg=5FQF5n({U3@T- zc};M7Q3jvehc9MvxR8Ps_G^%vJPhbY%3|1TMyH5tjBdna1n|wz4bO*7D`bU~w255U zH{YbJ*fa9%penz71OHamou~X*Id7%<_*Y*+UxaOdM^GmVm8JP!52AamMjU&W(JcE@ zeBOA3u({0^bFWH=g?zxd7ReiLvBY~NBZMn0v>GC2E(I0*p;B^7oHdL>G+umBh6V1y zaJ=>Y?Ksrc?4;T`5_P>Dv8?ZhzLYO7I(~f7=?V(Gem67%@;Ov6@hxr<;#Mv;2#I){ zrXpvl{z*J>KbP~kY>g)#;}ikdys+K}S`r`TJV9&YHZ`QR5#b4>1&H~aCunWlrarH= zp^vnYj3*xFTtRE2T>VXJBf=Hx|A8mkaoX0}D353Xa-@w4wJT*OgILgppko6k5?vOm zUD;wz(Pi4J4tf>*05(KA30i;sg#nG{O}am5a0on0k?3jEN7drc$;QAft>Nftxi__^ zE|MLfOs#LdkR$Hpr(zd0v#E${&k(40y>J_D2=q@m&f<7}8gevRSHsrS$XTYSb+JEj z1Fxa+2=mM)DEjjIW2@}U==!+1`szS3l^lO$#$Dj zFC)`YX8;boK>+RroF{O-aqlCyQ8lbB7hemU2yKFa3gB*(8$PKUQi!vXa2nbj*!0LefP=<1Y3VA!DeNmzth05~Y z7^13RUgBM{AQP_;CA)IBRhoyCgYGv`Vvw9Z%!*okQe^R)e8qPBxgc{RVQ zxb`W`K~X5|U&AerTLSrvS__;u5e!D_3EJ$=?^k?|_I1b~^oQHETI(-z8Z>RUp+8oJ zYYF{DWl^|ZWr}~y6b1@a7&EZvKpNQLL{0(Nu`}u-8WFPd+Lb0ctX7-Bpzt`-W+=BA9NbI-+Bb-s9kBY+irL06B0xRLN$4^mc#9E+8tW_ zc{no#J4%Md#!feFG(6sv0yo2v==>J&=%n-^MIAb=2&Xid+8mmG_Kg)hO{G?v7tL%K zH1zN923KxD{awv!&$)O$?Kvd7H~1ciQOhq$Rbx39A4Dt6ZsS^=7_}ymeDX5q;aUsj zVM$z!`rWh^NV3yfD95!2sZ2A{M8?4BPHSN>^I!~QHOS^74(XP>4NeJK8-sx%L{GHj z*8oWg7>DjlWZ8QVi8Q#hRjkhLb|(o|XOd1y3D{k**V_fN8G?dYG9rH+VRT3xms|pz zZjqzPlypsJ$KyU0SeM&fD}CH)38hAZNS>OL|6p@d3G{x9oVb%eq@s- zLsrWZZZFO88*HN2o1PmJlbCLh^9!<@o14S>jNB7Zv6)-7Qk-T-EWqKB_kqmP#x_nD zS*@BFIK$RWC578AHGCNY5Nm4MY@Dn@3N>t+$_DlF!cG^KT|s!F?wcj&I<UW8$9=ej~OO58uQn@g{WQqmKNX@{aWLG1%J{4>$j1&5VgEd z1~&mN0_KM?K+2dOmdWutWu4%4^h~b{@AG-z+GAP$=UR`g|E$Nd&Adm=u!|LSVV!|- zfR-Vh&^pJ_I{T3*j|>vLdWZJv!}O{PyE=@okS58L)qkw@7WFz&=E(>AcE;!Eb{@-l zHv$@PdxKl@7QnlaMZj-clx;OyY@*_DnrwE!7U}JF$>P$95&)+N_?4Rr!7+0i`%^U* zu3;^3A1Vdpva1^A0&A!@6p;6+lmFPK=6O$;E!6xWVNU#{hm^g#U zrb|k;dEMS#mGLICsaK`XY}PqT-F6)^WRND!@B8?3kr6aHdl&_7l60O{3*#`4t8Pe_ z;j+BwyS+oo}7TakgiX&A~>fi(wO`w3&Kg@h_jy`f5Hq@}_cs8`gG) zw!Tr$%`JLdhUXSLHt2tRH>0;TZ-MiEt%ci!)mjS?EkLs=a^A1Ca9bR2AvPj=YhxhY zRBNGJSgy4YwSZ}3Al|RFP>xN-If`l8!Uxe71_)cx0@>NLK@jiPER##4Z47|@g`Uuc zK~LP`zhr}jtKn8O6}O=kvW#mZ89Y&ICG2IQR$xm5@`g4RZUumellq4(ZM@uD`y_w; z7ydd-+xjw2;q8eBuzm#fOtWhqIZr+uk~Gc(a9z<8gx*e1XhuY|jem_jqTm9bYHdU< z47XTSSwARIXd!CT&{`m6xdZiqoUwmR zB%T1bf*gfj+>w3KS|H7dw*ZN_`yaKyH7;raGP7_+o9iuYkbJ4NQC9z*)<%@xZW#qD zd$bX?ko*K$_%b}RaK@I}5JhO!0nDstCGahHtRG@xNa*D$t_>OE$aQ6Rj~aGaeQEN7`52;b3jEWjoE1`p?xoG*lann5SE9irDuL7_lf~4t5=y#5aNwo|0AZtS zVQ!d%TcYXlZX_r2jCJx)`1D$u2Tug>zyRH&v}eaVZ~NE3b{)R`es9c#4r2$fc=hY; zs>i+DH35ax`*m1;>R@(hmy}5ltX~`~I{Ftx0pp+I(bpg*I2LyER`7ndaVm1G&I43^ zy&+A=f)T`+>Jfz`$=EtK!`4zX#v%g6=&{A7+G1mEsgfa4HV_62IkI47;A!jdtdMxy zAX(BMNCe})3#1!!RDMQ5*^n&V1knedyE__|>4uLh9Gu=(nmAGP>^~6NUcF!ROrC$& zxP=J^<8p1Sjep&gH^Fx{WpCFR7rPb|bnQ@FSgLGT+O95>tyX8qAGCRF{Jpb-ZP|hL zt6Nvasul9mlJzYdt#;<9|1Iv&p}44HM?_I``!{B9gpEBHzTaNL-K9l2uC8r6w9bl& zF*j?bYb~@*a_M{bL1cI5;vNM~vn3|0SEU7r8!0Jp+@v_{>2Tk8O);{NtHpzcH;|*x zBBk(jzLSS4hOI(Tu^;(RMF9#zWe5`G!EF&V4&5KYaWsxb1R#ENdDsW7CVQD2v-=?? zMgTkF4$WOU`q^GZ74M9{a3+G-!D<#v7(Z{``0)=+>%y0-O{wmZqs9Hm>6z61x6B4W zkB8^CNm-O#&=k=_={R=oeRqwWHFs3Ijvd?K8=OmvIO81ZF*=jN97L89hib5KbI&?I z_j|P`7+`epgw!K)@_9@QZ(~dNi8$Vdq=n>wyKpZWZ{s+g0Pld*Kk zYS0l^#jA~^6uf#2A7&O{vFwMx)`px+qh4^>&GJJwO4o=F=WF1c5fvhZDkhB(gwl{N zGr?Ewowji9(4~(dH2eBv4`sDj`mnEP$)jz4*CIme5u*E$WNzAG0YMNI#OTo+z+RYT z*1eM+9zM70ldYiJGjeP zjyN>5R~fqu&!*$q)yg$(J{F13$3WFOBEjdxH!?B{l0}N4EYRu1AuF~TC+f2L+&+G^ zX`<;_`O>l_dF|p$yFK^>`WAL6B1bha1FHr$CE&SZaiT!^jpKrMMQ{<6nGs+DZAfheNOo)|DL2uz zl|0#l`u9oAFR+cZ&KlloNO|v+yw>)+tTW}y={e~gI?h{$A0?gUEyFb5oS+EC>&m&*~pFFIPXamy7>PVm{&01TAPfl zyrQ&>HtbO3H)ZL*M?KwM=qR^o)uxq}2E~7B(hNMJCfxANFg<`)s44Xu$z$e_Shkb+A>hGTU9MVg7{!B~M zCD7XW&n@kI4*P(}d&)=RGR79to)8lo_q3F^(t(+p_GFDbgPwE+)C!x^xp6Bj;uEa@ z3R=xtlByYm7xZ(x`Zi91RtDf~@3Fi>srcRJo`z)n?2(vf6Y zKpqj)9N@JQ%ov%32!sVY2`faJQ@RmaP@^2)eDgD?OK1(g8F?DDCN__>&h4r?@}1#* zF3s`YAc<+c6NV^yhk+FAjN5czOVMI9fH)ya4nQ$`WdbeaD3=1lG@{6b1Y8)&6+}d9 zWOSNHfiZ$7rX%E0v!0|h0d-`bQO3G-`S;L{H-8~$$N$zz1?xNi4&3oW^&hM?3R|~e zt+2e9K?RO1c>#1}+1F;ot>ijxC1;qGAYa7daI`yvU@@a05whS(EP}?!my^UsMpp{V z-Uaf-!|LHXyn$Zo(ZizSuhNd-u;ph6Pg84~@H^2L4sm$($m*#hyD8SJH~LCPUxc-7 z2C71mNiT^y190fHr86Pl1ySBESx?Y_zgs%aRIp@JQwZBzcnX>7af77g!P(NGV4Bo> zgCuXPo408bdJgXsdd^4BoN5%RU!tc>BZh((f?@#O6Lf#-b6m50^V+4%xDDOvLN3EA~M*yXx1S|Rk7Oi1{pAQmZc~cIWbY!#KGwkRK^H0`n)t}p3l`SU9W}?WYy*zP}Mb6!RNZw#+|wH_ObT0#fLBa>#bPj?4ieQJYB7Z9=>|@ z5hm~4eq(vC$K_MU-hJ=LYnNztkOl~`5VI@cYmMtp`3Y;CL@_cZy%U)`EG8C3J76XT zU??`zi3lk#fwdAVAVi5Kx(GM=hn1kTR6-^|WDF3fNUp{3=`S57CM~JSIZ*%P{`2o8 z)FfRzf8a?q^p_m=^8EkpW}m5n6KAAx!L6^qgx~#p{&>r{eTuw{K)qLWhs+`%NiS>qA%77am!3ojnfxYM!&|r=E%19p8v_!Bw1|^85E*NRy|`CtGow8-MwXLOlk8G<%UFA6^X6WQ z%LTX{mxHwCo|Vlk4Mt+F2jLcphLwcwRjUdrfoW2M!rB5c8nb+6&FzjTqmu(&n-7jbuv6z{yfl9ZHLAM?xAb+_p4Z znprR-NXD#T65>{l-aEK>fHKi&E=2GkIk?9iiU#TqK^y$84M!z(a^MMyy^bU`ifcdq zO?`vSQ*V4fZAbs=(C6E?jodlpx#wU0>pdf$+J%7dcaEqZ6&PBmt1n#jXM{e}wYmE1 zirPKFWqIivwr$(}#M)&G2pQUhnb_b#b|L4~VV9XA17g@{WHx}4z^lU}N;R&7!KJl9 zr6i8`(n$o~i%t|hhf1^6&b8M^xeZEI_Sd!Ql-7l+XEe*WSMCrx`pwX z8LQ5oD7oj%PtDaIm8zAC4*}}=O7++|ebW8YhOghfb4t68@y#y3dha`@u6=~-q`}YV zf!DtgN}2|dW-*$eC?NK>GdYZ-vSj$UG{W;EG@@v9Nh6Fj5cprCzC=PvtLuYXnfPU2 zT{bw83paD0(oCO1N`lq?i3_!2oFeykJc!8ogS0VwXwrnfmAc32!Zf8m^^LC)3!x_Rh7 zunL^4yw4r!;}iUo@^`G3dk1e$9M93M_U@C+xL*$7PDj)Q?(~~MtE#|iGm z5we^GR28rGa{OS2;jd3upTBw>08R(Hec$OX>~Cx!%8WLP zGfyX92PPkg!DAe9r$8EV@WA`|cUDzAQPsBF*21o{R=xSg_J=MluYPD&cky_^!=EmC z_AfQ!PY0hEFt%eeSJL!hUDn^#Yi*mNj&Z4P9$4{irls`K?CtrMl}iRZdFQOD*l+Hg zhR01uniXalhyDHqbVWH#NhY)24UAZ`Rq^^V^9ahP`!PUk<8)?HlJGd3w{62vh%2{pYBT@2Qt%KavCe>V;kBFR1$`yz{Sr zLWS43O%~kt0q1hZCZx@1{bG&!!8*D36QK=Ptons^7YbS|>f4?A`Wxn9wri2$P7i_&YFjY&HnTOWLk++UwT=+eDk1M_oPA1kTZ{6 zzW?6P8ppTb-{0a)4Uo%0yTzpesJu!{Y_dMp7f@miF(^Hp;KYEP2|7E6-vooy&NL1- z#eKmk#Z`LZ-EJU5fnPT8Zwx^q{3I~rI@?@?EYV}e07aeiOJV*8w@*^manNN9OfePmcb;-dvFY_bBm8OzOg z-{+Kuoj4nxUfo%R*tZ*X27awRbX4cMIm!OzKkol#uX_C#^;`9Ahnm4YGO|<@ukeR1 zpZdtEY$h%M-Tee;E)%0mHc1(7H=sdN9Fo*LE5qp&5_NVj-~)SBrU@W*UYwYNJ^~^) zvi+KePf3MNPPJV%5H(G&)i@L{$_i2-Tn`=lE@1HVi($2{vr`%uEqqa-& z*~1@qH+BJjwYrtD&0FiYo&0dsLRO@nd1%FlCpR*A+Zy$^Z9(;)hsoY!Bu$MnK{QPb zo(qsO*A}&uhKP50CVz(OYQp4V?TLnL5q?LIK1g>2>5BobPe$_*G;NRcK~3AMfDsSc zpq4$O-7(4uu1|)opky;VgR2W4=|oa_d6R(hRT7 z2F%z5k7#!SI?fB&0k7AN7&jM68o4C978w_|?KQcA8^xn3k?1(kN*XsOk)a1Qvw$9q zc(n7Q+ZGKUKXJ(lbxDnSKsus!V=K;ma@ynF;C|qqB@fLEB~aZ9b=2Pvu6p?*W~t4A zR$&aE!yI84_cnzy4KM@PyyVYdhlr&ZI-TV7S`?=o7car*DWR~I2c`%pU@$|#;M1TT zHbirgPM9i_&B28$gtUW5RF^(O>iTc>=Vb9>D9gmmzwJ7+^WUh-w2RGS<=-9bwNELy z`Qrxldz4y1iLFPFczq{ZJ#^h5jG9N*AQDGMkvRDKR%_({51T}!{t?>9!M=)U=PP|0 zb}Am3=)HjRmyjU)A(B~v%p9B6r!qFy?Db;KUdaq(0kca^XoM{AXEAIjg+fRXBB12L za4($z6-BrL(L8B=n))5M<}r0`am9e;Q+kxY&|zUwZo3$;*7O4Hd1lK`vWJH=qkPEjkTus!%4N60=TGTvj`7UANtV@3B(ncim8FFzUJU)hetj@Z@*1tn*T}O< zNIf{ObA(+Qt$QNzBMG4O8~Rar;&1eX*j5NfU1-rEn86u!3Agx~Ne4xl47dZSE-h+G z13ZN4hbS(Nf>}UUQiD`Q<0gCwD*Hv>ibc&mpmwSef2aZ6>q4qjr6h)JuMK_*ZC_=) z4Qa^>4xZu?j41Rc>jhL~FnSSnU__lQbo;xya*YcPpi4T0+E#*BkX93=KY5`V!Zub& zqe5k$o&4#(;IBWt{QQB-=UL51?U%)+VCZm+L!(_8C*wIXSMe)R#95BSgHlFIX=r{+#HWTIX8m}$X4 zdr(0!Y@8$n94mcy1r57qZn;uoyo=I8Q~pF)*ihezDt ziNXHioa;_tgicfz_Uo)x$!0Pdm`!l%T@d+DvguZKE_r?u~pO}rLLpl^(% zFKDe0R`uqzQi)oHW?z~`m(>D$8;cr>v9YF%41eFs4A}&ZWBMI;ZKRiR!8lr>xd93? zYHNdl7LK8ie)j5>k1m|Mx@+0=gR?8f*HvAumq$N&DAo{n zX!W^k{kONu->~x0$5*X=WXePNeedme+iMv4S)AfQSVKE}EwMIHwCjvURB>=1OvYjb zf3drCuulMysvxZ5L_}I`Q{9v~ilA;f!YHPecJ^tTL27e+htn85dmJED7q1?cPosod z`k7Dac-9~9kDtne;ZsS`X1IwpEFwCUpv!9*kx9rY^`d&aj4J7}YzEPZdKoZbjVlpF z_K_b+zIl8f~e`quSZ}o!2YsGsOE%d{Nr-#F<1Lo6hetx(HBWY0kM%Tgpz8W!V>DqO6GlIakM344 z4e{Nh7@b+jFO5S%K|umwQkd@w2F?OC#v2x|8?h3^ffB8OfoPn!c4yr|V!T^IUqv}D z=3j&Lcq6>aUbDf8q`V-!TND++VLd&%8D@cz&qHz@DGKGC9;(7t8>br#5P)U1Df z^7$(@i&l;K>%JOsL}BL3FNi%t=jYvra^7!g6ssrP6*JXUkQvPgWs~?lB1#4nr3#}^ zY05$a&4w=$?KZ}g^z(ijP$Le-f3?~r!Y>kjPodZ_ozap~*hX;*%r>hP2{ba$=~9j8 zidR$*`w+t%xRFw#9aWM8!s~|L(wwNO*sE6TT~oWOp|hKx(>fCOr`z3!KB=M|?keCU z%kZYjI$)IZ3;-jlC_o57jW(Q_i1dNQ{KLBnvMsz;O(10ypBnm2?S*pfH-7;toGbX> z;EVhIa`sbAINBI`@|+sKe8ppMAMvWSIupiX!m0Hko;gKARVX`ZE_mqfjKkqY9s+`x zy238VR&(|Wjo_l1!hBVWKx-Xw(=5YH!)w{c z#=t`5wM%kBq7MRu&u39A7=p#EK#*6OR(@{G%vdVNi3JKSA`9r$dkazoH#rsscCmI> z@7ixKvEp6oI<@Art%Gatf}-y+g!065UYO7e%ATw^pdSB3eN3HNygnwC1(;*gH3rOO z;SBOGD2s%ADv-LVzKGkZL)f#qApbFfTbRG%}W=u-_F=L4{pAB<~A-Hz|S{QA3Z{`1v!)VJKO z&P2xZ?OI`0zz~+JGY}R*fe`r!gP=f&^B6z;^>R7()vD2ajKyL`guG5N%Racw`c{&B zC;}y&z{o5js4QB*@plj>*hq4iG~;rjlT0d(K!LbGU3`XZ*|`P_>Sk+dK0ER5Dh}ietr-4?dxO0xrsdfOs zhimS8U$Eql{OX?dt5@qSolZ}A>)-0lufJ4*r;dW4-;}MKol-5XMk>zQ{EiuG+NTD7 zeXnA)dhv4F^a!No> zbSJ`^pO|R2Sm2$s*v%-qMMW#Z^bqDKjU*(EpTT^nNl7|lFDZl^)97b@TRCgNj1`No zeyAp~t8CMfs;%lp_%Zzc1qPB(PfNttpq@D6; zX^Bu$c?^+=>a=>D%wWpYN^V7N4bUf71f=e&t6E$q9S`=zXG9@OT`Et+Z~uqvjEY(I zi0lkk4$b%fEjyFcL9%liWM_He&XBlc$fKK(>a|Th^{aLDRCOuA*@>pZ>}zn5UQ*kC zsl}^FJ|O!E<>>1uz6P_C!QHPQvz1HEAZe!w6_$_~Vx@AKW~f3as*Cs~yGd`w!2Vov zZXz`ka=W;DGkfi@+LrzJikiGx_5Yn+M{PoOO70o@PIQO93!M|QL+|tN0{R5f={ zMX+q9k{467!V_q<2Mn<~7&TJ^sc>7`Q~jy%eWj*)O3vZAxcqR*T>h)Y=E-GTsHp&p-H1PaWk`g*_9)#HEaIsWfcU0W|$iu-A=mfjR z`_oyV$-axt(}`_6a@&=S+pfanvEWfIF`ICLk*2IiQBu*QnDm0dF6(I%we-ve=>sbi z(Rd1+Qtn|jQxslRE!A`yen6E?>=Sx0w`PyQMDIg7U4uo0pD}Of2 zy13TUUcI(ly;kU1cb56HcZ{4PUaUJVEo;9y)K@(EXusfGkZGUkjds`!n{_e_J_>^; zNHJM040vd{tTU2(QOetF*P-H(wz9b+BRqk)6ODv%X-iXj*${C;b#-V9)>bWKXX}ol zK7os8!QblrqP^~owdPT1C^!S@(O?wewg;>YEi%7yqFx1pwj^;FX&Ta|y&Zm|1 z(G0BasCtDHKbXtl=!fH->4ct~17tz4B7X_5xJ&#^LF_A2ba0gDB0R3KpM-aV(w}S9 zt<~xdX)>`o>G%`qiRaiL!$&8KK+DL&gblqXJo`>q8Iuu@lIb!g+GRP$qCgT*ND{CL z30@0QL+lnuBFeph`{}$&V|%(L_ebTnhUhYnK*`w1RBR(q3b+^99bO+qcEynni@Sn! zdUf{=^;71r-(JU(>n?;;#2D zsa?yqYSgWp!RG^g#h?gACd)>ye~ECzKmmhu75#;^uLh36&sRi+z)$Ha`(p3SF4gQ> z^(yID-BE2q{Pt9<^x)E(#)33|D?0i^2?;cMl_h%O2Q~7!chN8Oxt& zEDfVU*Cd0D9DEufxX#a*Q4esT@rysPOeDaKb%7zAfs05ZitE zA|xMH`pAmK>)}J0i*Lp*N zUjYXRGGgOh0eDkmy@gvSxQT*v74rOLBFculAlQ&+=X6*xeE;7}5HGSE`*z;FF}G;< z0O0+Y)GMCiwZ3M@kFBiT(RM|AntB;7wx3mA8xV2r1sES`ECWk(ey6dJQ`G|I%gc|L zQ5uPd|486ngknc4MN37aY;yHQZUB<2#Y|$IP`xQ0s7WzHU4dHv>H9Zr>ecFM7Vrem ze~hGt@7L_yzJATloof&i@U!|JGyjh+3bpX_IwZT_MDq8gcR&0P`-w<*2?ZV@VRBk` zg0gME&lgRyH^vH*5)BA+H5edR8|>!j#X++4t-4Bqrm6s0pWh@Na&n}8`6??}RQ1-2 z>_64&H|npPZ)Ee5z50Kq5i?>KXRGO9+AoDzO4ae#S!Jhdn2KqyClaa*ui@2cE25z zPvKoUa6uFp#vSpxea%uNHU_XE9fx>vDe7^HhP4F1icsD4UbJu_6w->48H*iBmz#> zZgEnMp&qh)gx~xLBj)!fMbRNdggkEzk|gnDoT?P*z%Liis{PdQs@nX;=h}tE$^q=# zh1Ql!R1ZqWLpRkH!dN)Oi5*;u z+3f}=iS+y^h1ui(>1E^YTRvdz+#>{~fb(l)+6tG>s^#iv)l^%{ZdPAso`*IA1E^#( z2FWGDo>WIZv|nS|*clJFHqH)K`76Ft#KkR~>flAkX`2I+1#IV@h!t1hk< z`&O%~s+E)`q10 z_B;=V#!4qlS%#KSDMS%SMnyh`EF;7%qJ;~wj@P5|5Z&6u!628AHqO4Urqb3ZY}|!t zJfUg6xzG(oin!k)n(^<0HKifn9O$pJ_}WEd&TnmD4m+T=kQVVcHf;euNyIGkHlm1j zS{sya-d;OdjL_sLc0M~MzBkpHsNJFssBHp)bl5?%Hb!G zI|bhxsx}1GJgL0?BKpt^iKVbu!&nXbxK0e$kAkX(uYB6NLjEO{2lq>Z=v&QOzG691 zRGf5@b%#|>FSrgf1tpd#?T2S=QccXvwiL*sPq%o-wp-9OT{`I<#wi_O#NgooubXIS$X~Oq7d3MZ18%59XC`6F7 zL~TV!;q|3uh?zxK(z`CmnHFmUa?#CEEO9&>_9Bg6Jj6d~7vf7TGUHOPV2~_mL93)j zAx1*kBOBlXQa7 zV>UZ&h@^EHZB{+L6cjio|B!|VR@|soAs=3&F=CYTSM*yOZA@gmkwN1HfIf~!dSb~3VGJ8OMBzdo2y@vVhX1|D|q|d;!!&%F4 z;r(&e^6r^aC*3z|@|1hUZ|6Puz=FApm(c!gs{c&cFZ={gAsjf6!kn;NgBc1=nX)DI zR4L6^0*`_bw@qvqDWIn?ytK98P)w}>77&;1PfyS85YyGRqUliR$Kj&BB{U8eL>J>6~j{IJD$ib2=c$pj;Z#tPla3?@foW(ilCd z(OZq{6E%AI+rc9b9U3{9eYbP#`e#_XO?$*i!S~lm+2W4DBZpU34yU(vJ+O8QgRd^k>I*$m7C`%}!1hPjY1gB$ch`1^^ZK z5Ie89cK?;CA1^;^x5i@ zLb4Ewc`6eu>14fV;3ULFD6|gPzEl>5g6xnWdX%+M|51J5faDCV7rTc}u;q)P>zEeH z*&9P&ZNy06d69dgK2*AJPid*u=yawg$D8djqCGw_1+_af9f?Va<(1YXOG+RGm16_; zfIrYV&_5uP29p%<2|iC*rSJE3WNDC59Y)h+!eb3H6AU*}FFgh$Ihz8Hu(0N_=g&^1D5ovT6}zQ_2K|8GTZv+H2i*^s?18l z7DSU$MJ=xoKnB$4(xTMF#H0+L!-JCUqRZ$rC+V_VCZzVObhyhar3ACXW^ooS0Pui%fNe3<6gTPNg4ef06=CRr%gJ#?0g~^XS&dv|$@%M|1)n2y^ zk~V(!bqMuHE{48ey=yf`eCQnZmSzYuOFJj|klz)LdJ+2gW=`1@@6%;AHzPWyywVVh zStXTMipln5e{yaz6wuh@*kpHVs!&#$s(0H^3^+W`D2@H;_C>fHOQ9rWeGCEKMWdDl z3Hz2=ScLym1SM!lN>ESsXKLsdQDo=UF6n1$wr*bY+;{4gR@IwCantTh*6h18dwNwP zgLB&A;?kVOhhFQtp$h}f|Kb)c2P}rmy4jfJLvXTJ*581a{3LeP4j*B*(4(|jdO}JI zhslEscvA~8LDw=TEm`N%$9k=qu~vK$U!rpp=GnM`@fD>*ng_^`vt`buIoyE)gCqG> z(y@{B0%nkw8l&YkDt^v?*`j^xme)MHZe@)cbMU}{gQ2&o51)8$;?Tgr0iS+6b{IUY z#7*ipJ$>ZZw(UocZ~gxJZ!Z6J=iV36?)HEGO<;UFWG47JrLDM=5^*0P5<4wE-fm3t zN?xNyPR~fQTjNZM#VuMyi_Y%`)1_LZ+9VpVLiruUVZ#vTOk`K6L!5X~q~3w32l2Tv z5d^z86Bq7x-D%kT#D*QCN0;Bbp=x&3+kY9fe^IwiOqlV0^}!eS{ha2V8I!?&di?2~ zj~`Rt>8oCwxIoT+b;wk;-!iZW9RZ{|g^($x=kDaNLz#{dJV*2&U7AfJ>1bC12(@aAr`Z^8 zr15Pj6$WDDH(m;r85|p56>AK(O=588+U2|GTTR84*uVeq5rc+^Kmt`iof^d9scZk= z$E9AuBhMUtX~#1!zO?P$IkRWYy62uyN>vs1C0)FxEX6$akQDltqK=^*ai30)2R=I; zla#o4pBLFqPJ}g5;1H%>%iz)0n8A#T#v z@HE}b*2o*8MoWy9033_*m^o+dvLwLOj67CYzN(l%dJih6tl)?Ho^ zF+=Z%NlWnPrK}d_)LxY~*=&xLV_|uZMTAP@G$0xelnv46fn=Tk-meC>Q}J z7Js-{J~S3uXr8ek7e1?g`+V(7kNs!km^In5`bDQFTjxI2_uUUeyY8&M-y8SPfbo0A zkC&>|n`+@#XI|OI3U-d{H;k=s8F<>4u6|qh`jbrBFlOo_Hy@hwFlLd70}Q+mNNyIP zd8yl|m}Et^A)1*f2!uxwLC~)zT7YCV*Gpb&ijji=fg>J(myw;XW*6#?i-C^K)u4Dm zRl84A%5NT+AS10isEWV6MKa+T)u9B?kLKA!;1mkm!P8FhzDRhy)w9RAT_oU?=LUl{ z@=(>Gx8C?!Uwva3S}Ic=@+WAC;?3gqsJkN=M3WAO7!jKqECvQn2|&NIN|R=&(lGkIFgqTtzBJ|{FX&G7wYAXrns{JqWMwd#&5|Mg!DLz+$t9$ic@EF+nZA&EZ9C+3g`Td zxgr4DC9Swe|oSEK*bYXR`{PBW+(>4Tf%IQ%Xcw!b&^{}3%o2uNtT8E zTjq5-u;-b+B^}!z)_EMuyg%Q(df+8@mG@`G&n6Qu8_TNTEA)ex<&sp|+@2uWrM%Xe z$;q(;Dx>mCa*NUy%?4B^v?#Sh#l;&szb;oto8m%!*a4`Bi(=|2E-2r*BTr`w@v)9` z+>POgJstnATV7~H_q1?4l@|G;TG||rEK&-kKY5)EXuGI*>bh?C483i5PT@ms$8YX7 zV`!h@dBqPEPTtVtzCk_i%qv=4Fm6kaIsGec7lqQ|4bv0T194r8H%?1RPmW_7x)-jW zmXewj-?eDN^b|ld(Mg&pRLfbY3how=hodlu*=#h~5P!!Ez?gyydqAbhVAlc3T!-*g zaX_UDa4xw8r@23q#O~J{D+gT2f$fRH-`qZuut{u_G7*IS4XaqB=7{d9h0~Z%ty#=I z2z|9=Av?K@IhHYh@fXX~>&w(@Mc~O=_3BHo>mkhqoErp->ea>F&&B($6ejED@O5BRzs^@k60&6Qq<6cv5FKfR7Mz6^A&mjQ5Q z6@VoNUxm#iKcpuTz6E;P`9|3rVx0jc|uUkO^tUk$q=Lk8b6Ckdqr%<+=S1IaudVPoOo_zdk3< z#t9?jum8LLwL8E-SO|mbl0G= z_;y~WIR^V{!}>ZcPRLAdzhu+uh&SHfNU|syN^l!$c9H{wMvWRWWYnlZa2TB4zKxY^ z-!2P#tM@!tvwP1zwdmVV*lYhmPaf1wKct+12iil~5grBJUI|QGRKSgxq8RNSzen_V zB%9tZrQn9+Qcoj;4OlAvBe4uHK0y7&AA!O)MK(#V7Y{95txji~mM>I)g65r^Y~Cw* z-g;|1u90)69_?qz%*k>73K}XAi^OG04Lm~Gr7;dE5h=m(lI%}*!_uA*n{350hJ$A! zY>{`Ux!%E6C@xj=HG{ikx^XP^ZCUTt2WqPOF3)PWqV1j2r%fm>^OpqJ59$(6Cwbzk z1uLHN`NI4r9TUH>5YNoQy6?ie`-C*AjG5pHz?O;F$@FH)2qr?hk_b5D z*qDGpqDtA?TA<1yHY2RXf;3TkJl{twI~+EtMU!PHkrCor$+~>GZ(f_hD>s-7Ni%O> zxUPET=I2@Wfp-lZa{E1#rBh6)x8Lj9T%BFA?!hN_zkAonyZR3wR#N!@ows7_7ujNT zxHVwIC4YzBAWF#s-<$A2FbXo1$FN+^%luP6)rDU;sAmfsN}9?0J06(|vC5TfD8XV#u(HkpmNZ_pI&Sulv9i z<;oj#CO%x&cl*p`58c<}zE-V9cAxNIXhMfReL8gRpJ+<0U_W#y&g~UwJEph;b}J1t z*$N4lYMp_0s=2Iu9CWXNYqyRXZV&HEnPbU-~i@Xp)ssF-y(oO3_b+u=y1SxY;mAxPSZr!6exjj7XF=n7KDEnPVQa zhbA&dpVeEpu3q!hmbI_Fc6v;=!FgSAb9OHrSjonwLMHlbOLlr+^`>cpVF+8J-h1a= zBt2KCC-pVB8zq5ojarq)F|#in9(WW?%Wl~`*<(vhw%L+XB@uUduD03OQ@)3Jt&NgG zN1f-c6QA?ZX`zLM-W3x)`*=rErP^RcaESCx%Px2Kd1+7oI3#$7w688Vzf0br2ZvT|Y1iW5fcCkC{n~cFr*Db+UFY6?I+gZ8Eg+H) z#5>5Z9j@F{rtX6j0?&06Ej{c7gl7>_2zZ1Emc1)L%*jF4@PyLh1ijDPCcTy4pDpHy z>1Mr4SJcj9>s2WPS_OJlX1B7p^0z|G7f&lsE2Vk!yq=ZKnUd6iA@X~x9$ty?evOjB z5(%+^5;lr>{1e?t+WP3hP73gPW=1p3C?>cIV$w4Zu7EdWd5EljioDFZ4tukdxVHAd z;5&zov^&0HbLA`QnH`7szQcqTtJF6Vk1QBDhPkEkR$bJmalH;LUNGO6n`Sr8$s9Q{ zZOJ3;&x`kTZYfSWvE<2T)N|?wix*#H!kztAqRtIggi!uYf%i8r^&sKV@Z6n&ZZXWM%UNYo?@%~yh?YMXjCcvVp zt?gC1b~}UL-X8i)%$2HW4_vSo%!O3v;kaX&NT&+~k~6I)YqF#R?wA|sJcbxEz#AzD zsab*m8-N3BGXhOO@;E3D#;(YyH0HvsEro3$kP~!^b_Hdo>0pEib8S@c3bXG_G3)pn zRqAI?C?DLM{pPVxx*NKy=R0?uI(y{QT~kKRo;Jht<@M`K4}h0o-SsaIAC}&JL{;zJ zzjxKb6DQ`c+4sEq16fqgKo*#(pAG4~HLhav+iyknJ5XP=njvBlbZ3b^$+E)%35ksp&4^9RJ^e9x%}Epj-9+GT)7v2b zpP++cJh)lefu4zf%bJ^4uKwq{dH!_&$f9m?)r<2z<+a6gp6S>-tI*?i#3uqf1#1GG zLw6B6N2bt6#L7l>k1}OiL?_Xm`h)lT)B6}LvL$unxQ91D@_|=pcRCYccrxqc)HDxD z)ENCpk4KRjfUHImj412`&Y{Mq8gt>vMO0Bf#n(wJEPz1|q3j&BhnGhcl@mxxejSKm zJJ}f4jrDjTzkR}CThfw=qw8h^WA)en{>1CaQ+iRjHq8f?ZWAVXpL#wMh?eR<)whvy zlFc%aHFJSRj;JstPQD(IY<$SIabY!LEP7zL#8@PgDFtW5^z zX&8EA(=RWOyY}-3P#yQuvV!*UhwT1&6M6;zw*C3PYA{_wdoU&`K=d z*UTT1_6K_&Ieqd3qS`Sc;Cv_>KqhViD|nsU0(8mI@aVeZU)cXJ9uLG3+A40zmnHmHt#@+@SKkE*kDs~|<dXVS{Os&3-8d84 zE>U-=V*NsrQD4*v0w5zOd?0L$<6;y=I;UoYN=jNZZ{NOoi;|y;+qNw(Dk`G03f>4@ z6hdBU&N*MV`VSe8o3chCjm|sK2>v*nbw~5&Ze5(*GTog{ z*^fwW4kI67)p8prz|qEI5yMwEg;ZkVe8VY-*u~vhSXhz&&NB>PeFiH~c6S(+)8oMn zpEIUjtDjms*f3z&^V0Qn2HU2ErtNIoBiU-Ts8MUYkbx&F&<+HhVhwCVMJqdR>E^n(%0j^8} zBzhOp-@QP_!)Todgk8Yh$bumr24@3OjYPo?G!mfS@Ph{p0?y#3 z&UnrJ1APF2dfX8Y$w@=Ah{9O-Sm#={OC5Da%v1jdJd26yN%qbAue^Btqr>t|6#e*O z9R#Xz?|dXr2xc#P^6~k^S!tYG6-2in;ug85um-yAW-n|NW-Fp@C94g%NHSt?fh6D% zY<4pOqa!(nU=K(>Z4@sM86uAuhy=cqzny~Lwb1*$Y^b^yc|b2B9U$<{H`SC`ShZE| zy+VCWJ%?m~6DzI+&uL+tu>6C51Mp_1AkGvOk=!0#Y?4KZg(?zAO+cn@f~<4-khd%O zd>)9|uEqd75ZN?@;>K_U8WG=u!y{321D4b!q?1=RLs|8i8uW{Q4|PN#vHxv(b<@Ap zlj!`Hzo(=2?dr8~VbyZ+FMJ zW2bwV=?0INFZkwH3VPmMv+~4?-t@I(d&574{5*uQ*`_oB_7#g{MPQhl0crs}#DJQ( z2FWaOPb?2cBM((HhyLL*q&pmxcz zrD9EJ2z`^MTk3rN$#G+M6OF=(AyZZejR08Siq2%wSyA~C0W>H$02DN2z?zU$Ci1MZ zKO6#T%kWX)*h|QO0P7O>vJ186XKtKzu9jU0Fne~pRAVA#f3UAqAAKWxAZP01lSj}9 zz|Hl*VTa{B9=7vzo5^GrB^PMUpf~Fi5|QRcwC9Pn*lcEpMRMyUGx?<{0Tgryi&$7@ z=dxg!nMPJ6On%zxh*%w+vUJs!wd!ZbS*m&`i0&%WZ5Jn;2u)y}<|JC&X#^`2`6ikZ zr(?Bxv%(>{-6mqJxD|9Btd<)m8kwO0?zSm97N?6vrh`5{!5~`*IW9KFo$KuE6rG6R zvm;?uC)&U!X~cALU=#YIAvdfdxDY=yZXa%aeL(KX-mZCP=BDWvYA*mKwwjH4ZKA=H zQi;+kw4zAbi+p2M3)s0?sdrwYbw!-EvLEZ}hs6ppODW9m2qY&bIYg${HA_oo0lz;0 zVab&Q=9nrbCB^zBhar|PEkEFVkWD1i@Z!cWKy$j&Bw(2Gb*B7mDa{R>TUMw97ywy^ z&Dk<8f9d{ZY2C+HCe*|azpG11)v?EN?^-`c9WT8cGBKdPFIAno-phBR6zrK9qccKX z#g{YhoPkjD*Pec&kDmRobhBDavw*2op%qVo6|20o1&GO!>W}p&(=_zy&HOAiB?U;* zPMa$k*&1mgJWeTg%!4m@!#psVMxu7ZM5I5MhosFzUas!HV@996r)rm`wy!8NZ8rBC z)Go8CW=U-8KGW|||0<<~4xt=O?@8(oed_^}AZ`!1qgP_+SJ4z-I!fKo26UX%Ki|Mc zPgeaUU|`8-R!Muy41c>jh!$or|mo4mpx#L z^y=bL>~zbG+3D!m*Dw-)Xhj96gC`!`fGZat@#a_hpC~A{4cvc)4tF>}P)5F7L2+0rPoDNgs)n z6n$CtpJOIVm)qm{=4X>GTD*AJ{lmv@8FJ~Rm;Pqgi7(!G+HQ3GIn8-)?u6}oYpKyPLFO+RDbJjIzG* z6@GtiypiPVA8f2IiyuB>NdJMF8|Jj+f!zmI4n(sK>|cyEY5{C$N!7vXAe;>sR96=i z8@c`a_k+2ozdtuWrRVTTDbd-Vq~M$nBt4X)mHqFaRk1A#w&>KXIQr=9vq%%+-oCU` zl4JY{h2(F=1+lhLWL7#9~<@E$uY4{#|vi%(BW^y{=t*?7P2zJoSp+qb5zb z_Sefw(#D5=bHkK79^AWAHEnwE?nUqcFmm0*y6b*EW!BPBYbW01Zc3hSp4~s^mdkMU zfB3<>w;Os!kDgWKZg4xs!UF-OnAG_4FxtC~KYY+PXV%Ywm@6 z2d3NxBc6QG)!-VZrDGMJtTK`_6ERID`rRrShFL^UiG42*YqkK^Y$?%iiRc|KOdFh$v2b8?K_O!&U1Si-$y)UYOOiAKcXQFL;I8_}X2MY4lItth%!MZ5;k zWyIlF$UPoTGvCM3cn_>>J<%0IPo4bm#2-$+|NB3D?3(w)znW5g@#lW_gk3)L`I|?N z3=NugbwL9JcZ)rbf;$s#>gVIX5DJ?1wlY;|zdf++)*~}}p3+yI%6(?DwJKrOq)lj# zg?6fMDdd=*WFfp$To|dCN0*&m?eTkhLApzE6SzmJS)Ay#^7D-x9O%gLW|;1>9)N~glo`VPXbf3Eb|3(YEZ7=LO zZ(f zZS0YmkQ`T@U6f`Z0GE-Q9hZPj;?kn>3MBh;yJg40W{*=t)DHezEx95^G#csD z;u07p;a^zhy7Y|nfcR+Oya%(DzsPw5&H#>l^hR7`zuJblL!W>2mal(pzs9(OpR|r$ zwkDyz&#cUwuCrSc8>;)%C#+ef7kz1eSk$*ydA~wc{P~wEx3FK2^4eni{g0n~yL*rP zk=@_^wYso>kKEjz-C<=V%X*jl>M-br!kg@QjTM7K-OI~W@vu>2N7N^T%=0Fs={N=M zPZ-xYBAnkCZaiyJY1oyMUIT|z6*V2KM^fWV|L?lI|IXo{tiwABI6UGrOkNs}M~D>- zf%1gD!DA@tbP=ih$huKEkghb`GC_9yHm&2AYz>X&ovR{K+>KHIotSoyU2yB~R5 zzy5y~cQ5QdrQcxhYfB2t3u~(VIkV;#_ALBv_n-l_BUF>>C8nnG?!OpE~=-wK`8-Gwv7N%Mnf;VrTF4%-%|yeZcCHbJmau|9jxW0egxAv?gszV|9u*r?j+Z ze&f)@k%Rhr&9lb_!*ILZcl02$u{$i-xPcD;@4BnV^mj>f$^B;W0?z?LzM#DYiq!}2 zzk^+6#;2r>7UntJ@`n^THn@l#02hFR(zNuJmd{)NY-J3K{QmO>t6p;#4xdz8x~TtiZ~prn|M+!_FDbul*~0nZ1=p-> zvcLIyQMbZwXvx%p1Hnc~xT#Z`n{{jT?|x@b8{4B;ZT;Qvs&CE81>yV|GvyyVk1Q_i zQ8?4;K4V@{dC|#-`j05>mugvA+2Si+7f)W^<+%Nps5RnbcunuZ+P?iWGE|^%Swf+A z;Gm>lCB4p?)GZXsD{;rgB{s$-k4VJAyD_!IRac!?eb%Hdc^zc3%Ll>W;d0rDheUQX zP(*ZrG}wm=BLD9bwRA!vDTQL%{(%@mPwi;xymf4DaCcQ+)$^5kV~xS}Q+o}&?!M1X z+n?BNSM3-b*R*54BX^%~__<^Fe0%S}8>(Ao-ue7Hk9b|#J7(dZ9ocqy%m#X^eR1#J zRh4Rsx>Cd}W&?$o6&E{3(2?LSF@mD4@QlS>zs(aP*!ER&Kj*=GIO-ssr2?e{) zu$#KKMJw4_bb-A=45;%SD>z5p{9^`GXa$&E;dA?ta4tNKCdZj+Ce9jVrNHo)f}?k0 zur|f{jkE0NFooumJAxsxI-%2Q0>B@zCX@p6o*@w=GBrfCNk*)KxOBt7dHw8&2LhNz zp|?aALG2g6WjIPXVkS9f>s1P+Uv1m1I5~0lw%%2@&wlc;#~$l5wzfGH?)L&JRh8#w z+*W<~ zf9Lw#c+Y(t`Qs$C7m*d5(ycylCnV|C}IDPn`>$rZ{(}xjp5mhDu;d7IrXk(WsGBu&hG8v_% z%6bVaCH3Lpep5hYi@tX6>Nj+Bc9uVo+P8FMBl7wfc^!%7h11EVj=U1|PI-0WQvFDj z72UfYY8+YGH#OkT${sxwhW|30I(f-`Csqe?7XY5NaRc%KtcI>{JnMqeA(Qj_Cv@?q zj_Z5H=-#vZbLVN^@Q4uu&mKCY|L6%9Up!^b%nK%8(XD6CqUy^0Qlqaw!+ZAVIagHo z&A{=KE9Q(o+v|%?-@O#RQ_B{AvJnNw7HukwMp176 zuv?Y&?4A?s6PM^8&{$XQt>~MT*43&kNiM8Tf*D=T`o{*XTei~+`i2bbSIQlIg&7nd#1Qh=a1q~5*XuPnI%DUZvMLmS48F)+HaE0QnVTBjB=xd)Hw*TVRFZH1>*AENstC2Yg{)%yZ zcN(m``Pc!eOf(a`I3kgPcypXtz-t(@~6e+=Om}8w5))* zGmO-N-tf;nFv=QWJ6#+bMvIUA65JB(P<%Dogr~zNR_s`Izpw4_!|E$JG47pw)vOt& zoERU`Hs|J>jpo+j;?+yvvUzsvWY>s0*KM0Qe)5GlMh<4e`da+w%O2mD#xiFwGEIta zVUN#;(;l(?NXRnI^l~c_t|+2SFW2g*g0?SJQCzk?&J^JsB0RM?n~~5IsB0EfZfsE# z?3Nexk#HLIH+$YjHRPmPy6eDkSL^QG8)P5y?cd8jq{_PSdXW|J*fJXXr4mC1I{_{& zHPxSx0apVFN!TxSVXw?cN|u9Zh!y05CmcA%fzMOuFf#pfzx2mT= zL6z7iRO5%oKRRjWi6Q-~6-DmtVei`O61icO@)8 zQ2W667qjK+?(w$2o41X7V#HIpzALgyABNCb%CL&7>KYGMo4%4jAT2$$C?mhHz-RU- zFUrr#O-4u_xK?weVpW~&hfB3=4aaDr>I^o5WM%P#LQB89A)ijDu4*S>9~5QQ!@#6L zO($9l{qf75tWQX2UY+G@eDsPx{LixsXRI84#r6&Q#+J&mC399>vLW^Cad+q24W60} zlgHxZ&p+(P@7y%@@l4~>-G_?)^jgB^u`RRya~$haMLG7N2NLDKF#ejT}f~! z8IMT7aES)zUAP#Qh~OSk+0E<}r`8#6&MkIP53w`t^0i&2w%Ze1Hf}^PYg^yYwn$&I zL6=+X@b_SdZzg^Z-Ynzc0s!MaDcK!o#tomOo2EA*0nTj`;8Xj*eUF#sBcIDN1|ba8 zWV^X}^Je`Q#`E^}<#x9Ee7X8eYIwlbT<@WV*&1S1#o;PwR#K7|MbfIq^__ z(P7bW7aeNZSqpYHy5(@949 zIs8(tOK1aI+}GA>;v~j?Y|KBBmUC~Fvi~U-P4R_f8sDQ;SVmt~YEn{ivL48Cbgvl z?Syf4%CEei*=GIv0SAwN_?Bg3XX|$R*#ubfPsnmoN~dGa`3^0ZCQRqCEF+MZkN|HB zno9E`6kb{&#m@j2$toEllN?CQk43p~iU?lAKUix=G(&1i7^v8vMVWzMYDkU(oDuLs z{bA?UX+IkBV!L>UkA=NJDP8tzPj|Fons6P$NT(&m`QWNPDUf2Jlxc{&rUwG?hL)0? zWZ@*JoVkdj$44<5V$~*gL;2^7GX9`^om>mge`)Kq&d!BLIL?Lb$(%WQwH?#t|9$9S z7&gC=_8^JQ0bgxTVtMH`maB=h&JVE%iRWQF$FSscAO0=UyV6~bwikcZv7;YvUzqxy1|Bq>ZI>L;e;06p=_y} zwn_LM15;0^RLy_Q9->aXW-nW|ZIgTQ?%m7VzBoUWIkLA}W%p{kRd>y?XS&`)IsMpc zS&4Q_#JQGi%qQ?G=RuSTOff!P^Tr1ZtgdlF42>oHF8c-10y$#^$1vK~1;U30&5f|9zHhU|S(ZA?v@K=6FhKcb!z9*~z6$Kd%X)F|0NW?8IFoPCn_$?=_ zfjjBPnZXG&B3elFe^GkiEpHs!!o8O6Ter5f+AS@H?nE{!#M?NWpW|L30_nod7Pi%?Ou(vT_zY)zB%*zH zo-;`qay&x*FAb3AnP)$-qfJ8tXsXrS_4`jdGywU*?f5&z-lKxAM%@Ruf;hqjr}fE7 z+{s4kkE82p^vN9Qc?et7Lu;qXJ5?9G!YPDQTzA_KwY%wFi@i{%+S2=)uPbWZm8T2c zm?%_6VqyT^*1C1~V^hqZW8w@X`o!bGsR9qeVv#ASb)x2w`t~)4w6_ych@XzBH4&|b zdk2Uu)HbBW57Y&PbN8uTyaIZFLTJ@nyQX?B^hs})Yo?UE!| z%>RTfhWDV|FFU=%{V{eOOSKy8MfJ?KDhtZ6Y%|hx3oMxMi+hfoln^W~MpQy9>{79B zi{{1ErP!HsKW?eBRmG_-RwW%e7hQ|JD6E<#LZR#?(B_>-XEnLb*MMnvv0 z_I2;N<*GrGn;QC^|J2A&=?m@sAkub+PDiCI&p&6;}d z%!PXM)ApeW-O}&Nno)>_%>=br-BNI8PTOWI3*R(nshlm}!u#;QZ2QSK4m`2eK5sC# zY-JDbHPCTpIMSVQCZolX52QHgdGk8!{iTeArhx+u~Johil+AbbbdaJz+4rY6& z-;#3lPj9{*XWaequU^{oR5Uab+3icy>1oV3ujIo z-D@RvFTcG~B3WTCI@p-)iHGhL7q8&hLLwf<2jcRf4ym&y$+XMOIdV(;t}`zaI%K9k zw5wbr?W(O?ceuOWci*XHPGlp@8}@1gVaM?WoC59e1t)LpSvkg!$dcUoN=&vOSslLt zXN1OVdo$<~Cw|wq9uxgjYK?zz9}DL|w0hP`!%9}iHxsq8Mw}E#Q1C^V?1QgCaRaQU z!A~e+MQ~P(s>1k?=nOpC9ZEr9jq%O)xE)`eo8rqG_T>(H92Q?+xJI|#|G`DKJgr}H zYNw3*$X78Nha0=OeDE%=d*b4gVVUyAC&bG+ZMf@+Q_0X?@jcg zQOg@@hIMauKcG&BNCmiVRzW=7XM<-^{U_~XV zR<7!;SZ^${=kHX1wCkQ%m#Mi=+jWlmzOrqZ-m33z+k>m`qw)0;L<$I@pOZDjIyU3t zaMmPIQxZ-u8FP}B=t6jAxD!uEGxJ>v^Ak>~$_#;XQPCY2^t{TUa!(K#N%7rm-aN#<|Uee)F0!=vz_&CY|Mv2HW6)rQ_fzjU6K$c z%m@E4J_8#EI9L>kZs|z)8FC(5z+hs=1zaFB6uV{Xsh#!&SEXH~e-jD0#-W99mmF8_ zd*NFc)oMh%7MvRJ;Y*0z4*i73@3Dl^<8@~Z=!8dU|8#7CMf8HGPQuR{OAwl8So5al z*2VS<##n@&c-M$H|2KB8m0Bb4rW^6`V6(1k?2akT(35eECIQ=Q0oY@42{Jv=IAg&k z;@@%3h^^RuECSp^4|R$G=XCy=f#6V%P3Zr9=>K#nhf0g{BtiO<(%|ynzy=!zy*E8M z2|los(h{PYS?6*%LogQA!BNYcT_`IO*#TtJIkp27G!cOI_zo>#ABldKj{641ZaqFN z&6l2NS$==Ij*_Hek_DIg(tU{Zh)qB%kOcJ{qUh|coZ*d-W&?IgZX|v=1}yiB5pe>l z05$>pw5;B^?52lz?AX3ynXah?5*u&PHEN38l31@FK}@TO11+xcHlbW{!zDw?rP4h( zfN9~>J-n}`qo)vL8}=3iB{p0xKtfywyl#`HBG;5fS~|!Hnu_JJQZqUh3A<^S_4=T- zUSG6eRde(8*DOlkf=~$d{M{aq*U8LDPBe`3yL|WOmi3H>A*)}BI0;`kjTtye9)RQq za3~*37Civ(lYxM&To3^hd?_O1Y!AxEVY8RZ3Xur5uo+qf2U?W&3Zn&HwtIaGw6twg zUU;%?w_dgQn*3lJ?sYnAU9rdUds;x^`v!ceDM?t^$0sHE;2K2&Lh$_m8Q;sZc*+qZ ziB5p}g)bW$PfXl|CGG{guY7o~y6vT^kv1ZU*hxeCZ@*NgtF+AO=7myKi0GPnc0zV4uA`(R;IMi^$MA9DkdBlcYk#)Bv_K6u zCUE_^I>gv)mJ=CT?rVtFW#D3HK71PciyN}Km+|RgSo9!|he}x}mrt_}A{`n@YODTlVN{^aA^z z|50(LM3<_@E%1}B$yoaq%k3o7hk;(0mev)c-Ca_e4j0QfqnhANfVW!R{jaRsQs=-V^Zm0& z{??Y@+3~AO9-Kb$fh{-PlcYDSO;z1mT_=YIE9+(z9Q?@s_a~R`-QU~6eEnM4;#t_;5C=l z1>p{HI3%VC$2tnD>{wq##;{bYP`aeOUT5|hsaZrJm~!s8!Opkxp~FtvpjSf0_NuB!%HVSt|-!R za^=A3xR_4XvEw?|)agAqA-<5`#$#w zb|Eyr@eoJc_kqUOllm=eFrMeV7s9p(%NFi;_f>x9PI(}DD-uR%_lJz?^_KI7wham# z?vw|j9@??ybZ3E=aMdDDg3$`8M&J)f`9W%NYn*{MV)hZDv5gAt^PExJI7R?z8 zPN)^|QW&vBhMeV)V@3}K5T;O{wiY*Z^fiauR&`Nl!;ci~(BrNfcI~~-y>9a- z;@+ZvI1J@(O~No4Q9a=&%Y)*ujlfWdu=b&BuQuCH=S;izns82f&Vn`h`igb3E-`l5 z_u4aFG?#nS3H2{2g~%785W{*e(a5p?k%C}68p3~Jg8_42Ab?0MMlwQ|U{5Ni-Hg^r zZIW5A$T?ibJ(-w22L>(skM^l?*N$1bTCI&3wl>%=+uKvRZKzWJ)(f1gEIQ+@L4Kw~ ze$rsED3M4Ua2=Wg2d^lJ1mTDUOi5rUj!yeAqCeppK}S`NYC&{IroBKgRp`fVoH}pj zvTNEl;Rec#?MG}gyuM-o5w*t&vV+KMxMOI4%C=eQK906&xAsNE=Ksywhpi22DN*6! z5Tvzl*PX9!{_placYxY7tc_ytgR4jrK4s0Qz3;yn`q~>K`h6t|d}L={j5oCMQB=gv z$FL(#G%Xsuuq3BWO)WA*qyy@-kp_FG24;>b)e1_rk*}iBjlnn^((NC2s*d3-13IF_ zc8aUKb!*4S$_)Dc@(-dbi%u=E(bAx`cHXgt6{qO%SJtk*6B&sKTIydHG-^NXWNk#E+ zi!*?ze#vlhC3fLt$1u#39MV=|n2sJibi|cW6dl3?WB(wMqDW4sXo_XM7){ZUp)(>X z_L}=6^CPA@9QB(9`%fW?j`>lmrPlz2IV{%7``{Ohe=GB&cwvV(irGyj@mSvufT6bqVET?eshwIGVxmKS%T{D7#P#)HG_kQ}x!nbdN zH-UL}vwf&-hpW=E|FiGy|M**1zv1pPdUJ8Ey}-WT-fAz<|1u1RN0Ux9sli7SI56!key!8+LTla68r$2JRq^%oQZA@s}cbjX9{SI#Ve`g<1 z2_Ikj#&daPca`S1W^mB5-NR?bd;F2t@v9Ul)Nkcq7BP%7{=kAi7l`f&YH5^?+ zi8BmO0?yz>G<>mGoUsUoP$0A>{3qmg(2{VDRE#zV)|{aVVJ-Tzefr@GKbZ5{!H+*& ztG;e)GFBS{;*P!a<`-vgt_a?D$4$5^_`!X*qNrLnP6;DMX@_#TTqsvuq+IBJ+-QQi zHUnW_5xpyNCgR7-C60k046>;qKG@`zo>QzH>;Jgi{{3J7e9N=1KlJMF4lYrzwyiPx zsUzMi58nKN{q?Yo6ZUS|)^eq?Z(l0?PyZsGo#++%7g1w0+{Y3om+8{`77L+-tjixzZx zUOUQM482z@7vX~GUJ z$G#%J5&1$`DHC48d&jKSXfO8aW5GuZWh{LO1zuiZsi z4cJ%9xiUOQyTMiQimOGUeR%r-TBum7bKW+b(>5h|UIT6^*3Rei75YB>TfyV0=GuVw zEzCXn$-1TdiHHgn=ZS}QWzxTlHxU72H9a9Y;BG%}0r!;cb{lD5$83&f7R!Bs%3H3g z?L}g)zx-KyzN)u(t35A?8UGpeg1y@quTO7#4wlNz_u_iJnDL*NvXr1Ki2VDLWx*mg zT9z~{W20w3{@1cxtpfJWh!OuOm1%#XWVlaLA71r^cgV4SQe%)`8F<>eNt$mU=5 zO8EK7-QQwgjB{q+8s_c~UVi4#3AOE=H}(17k9=3XcirvlZj7)}h;avpqmNJ)Xhn-S zUlqV_NMSc46Wja+2y0nj7==Zi?6W3i;`XFpf|})Oxg#f~iBT%(nD=?8wZrx-^i5fy z;pim{4=kX>Fs64f*{>||q)1_L;j3>Sz3G!LVFk>;v*nL7mb4t%bw)8b&|CZk7;XOcTWXO;4WM`1ZD~LKUuJSnAF-ShL)vr<$dLiS|9=eZg=Nzc`Di~L1!Yf*%wf+Huvmsq>vCT2ob zFQ)c$&D_~%0qx7;Rn?Wk6X@;r#l5Y^54`l^yBn{c_2jJaQ~!0t@BaDJCNYY4%=n>UpxBxhC5M{v6xj~ zh3;7-H3@Vx3*r5&uv>0Yrl+_hFBwOrvHHicGhcQ#>`2*uAB-ZtGwruOtVvOjn-wrY z%bkyo?g5-i!Knb$EZ3lu&Ck7J-{g1ScK6zOD(~;3R*$JY@8Q|Ij=gyCQg_@UbO1W! z;663x;)%HHUzoqTwDA6$;1nSB+BwIh7cdtbMK2U!Kch6OOSgi&+}!jo1unm*u&9e0 z`BdohnAZyedHE?&HL72Qbc8fm02PjJ5E?p<}_ytF;;&5$%&(f zKi;khSP)va{<5FT{p$fj3*x%SyCU)FE#w(aQd_gGk- z{II`vlQMe)S>1B7v(vf-j6_dvcf><+VWx4p5C#c~f48)pd|F>3D*t~gv$(sUXRLe_ znB|owhWlNt{R{MW?L!-`R<)}Z-M69Pp+hgcd+6u$A81~$dcAhrj&Wm$tsc2?^)t2c zMQdhm;{G{WRy+MAJZ3$EmJK&{O)coswJS~$Bb-sT7w*SknKCk98y-0+7bo(fTgj2p z8WU(}#x&JMjbnZL24EkHIs9YTh2lQ01bamH^3mtsFk@(Aa6naK&we9A`d{ZQoWEfE z;pbm@-3{yA@|0or?kc6#k`Plh zDc$Ja&ErYU$-yz3oMa;_6=(F~Q-8oM9ZM^|L?Tiv`&W6L?RrOMFM7B6xxLRHvS00J z+FPJju33roT%E}2D~I%kAK3VC_f$MSWod&O3o^?nK<3t!>y;7i3$TU5nCKtT@==u$&tfc@$g=3WF}{%`QaW* zs5xrgfnT{F82vD*IA*TSE!ihx?;6`YN1E`(-G|$rz#0c$We&A9tXUNubyTM{-1LPP5#}_Fe!h4h~H|byWEJoPm@`XCiKt$>o{;6mJs56rX)e=5hQf+w~#imYj<^{TNB|X!j%Spfqv!p;glTsPr=NgptM9c z|EaljXgG-~B9i<6rlCaYND{z}HnE1-tJ!50jFd2=iBBq&av1pu?83vte%GM2W{W@p3nWz-xrFu zuioYJ72-o2zt`+7ojf1Ua(&6?({MJbqvzvUt|iXt?hy-$XBA z{XFU&X>p@Jvy8U5FC|v8rLM0cHO%PP;%KRtVkKMZ+88OB<0f0IDEPFwCd$gCGJSwAksBx!1bMkP8RWuLx$w(d^HSR6uP}m^{*F3L`OqK$p?j^RT zuAGf;kpU^k6pkpC1MfVJa>z)?i?!!e+TGjpS?t>PgM7q_8t9PEVi&9#XXJw#xoSBw zo$@R}L7fusknrbI9oNY-n}MI_4+@pe0Idp?VQB%g~|8kswsd=~#yK9nq{ zJWJe5f3!T)DOpZFOMWsR;e02bYu!sC`Sk5nuj!O5C!cFwj+M^P+nJ1gLdiltA}`V> zlq?y6;PR?iTSKy@Q?ihoyy-fOHz8R`l&oNf7Wd4cf3XWBC&Wc?W)HnQmJ>APEXhf~ zP;$_&!L238urt9r{oW~u!P;t;MKAcN90qgV(PMmqUPlg)9Gzuomz?6xIe^tzh7OYB zY}&+10akPJKnb=urRZD_VKt{7YTQ3r56Ac~IoMky;((I_<(DJpSLunJ8=Prl4;c4HU#W9TcMf%-{OOBRQ>Csxk^S!H<;{(6s z_z*1&Z^Ufv^w!Vby2$A*?Q18G7^Bd8K6?wJSb7Un4ss}t_V<}N4Av$)ImF^%oSDPm z=a@rm{%~@T-eMV0j|-g~ay!(+y+P`sOBtlM-gD~VxT8axI(bNMF%SHH`H$6uy~TQ9 zmSQQo#p;3HLfir;2ev-g3pu0pC3=AKZT8kvd=71ir}c|@S|7b7ni<~b&IEgFN~9Mz zpKil*{Nq~6-l~t(p~Wd-%qKp7$=<>*S<5+2ExSa^=;VP~$~Q4e$DMxJ$s=|?%-I)_ znO5?^OnXb`wq9tz>N+O1)Q%xDuQo^nXKVVY&k=86rxT%0gMS+rF3ljqM6&1lc-(KS z?gCd^a?}7;S*&*o#1jYnOJulXDSPLIHBT1p&bheC-gIHV$BUjr%5|d0!2xLvj}OV} z`8xlN@3byJ6obb-%WCW7FneeC0*c`pmC+V$eNf&+5D5+{G8VPK-MLl7U(EOUF!We);_Q zX>%`>+HFMbjz_-QqcMP+nUXOr>2lpp(Sw5A=tTU5YmXV}N5U3BG~OC#TH`}%^# z<9F1Ia-A4>$%Vr*_~9MX=BHhLQKYs@J>Q|W5j1d&Ziz18U1Fx@3j+b!-Z?bXM1MR>tKP7iD?I zdQh_2Xpuj8{W#lJ1WN$o>cz#SrIloN%kP$D#lc#UuO%dC`9|7{SqO9ZM&vBD{}tI8 zh;)q8LXJB-$2$(3**P9ZQaY8|?o~8=@PM4s9!cjXUNWlatik=WOM4_tj=y+X$9DAe z=~mRODmZ5W{0>!B&0c74Yj01K7i%N?HP1_s*i@~sG1&`CMF4?28oiczeUeY4=49|4 z#A9ri(ukC#i zxOnYPe$UmYe)4+}=`cEd@94Be6CfOYI)Crv)GHw$_UC7Q51#T6%LDPZ(tKE8BpRtO z1?D22hbuEPJN0@T{Khz}wrVR8LXGtldkkdc2U5|g40hFW&p5MoP;Jkm!u0+xuJ2S7 z&ypEk%O=O=78P~zH2K<&btsV|AqkL>LaldWT9C1VC}DYE`{6YT!+V(Mp_C1s~qJbo0!M2L^Nd7FB;eYxUwA z&!6SL-0Tgy)UxcW)dFoJZh0YosfkU-1j#Ito9vO{*rMf+98bV)fd!xSt1oMqw$c93 zZp~bqGDCI9W!?X4F6(gQ!pWsb=8+l@*I-ywAn~Uc3rBMWl;K*4d^5nEsp4BU{KCsXwt3LK2`)_Z3@#i<+`07h(n_ehF$TbZ&5R1|M3YVs|ctjwA z1Bp1l!r`%qADZZ1BCo2}ggjnv2||R09`X(DzhSakeyd*f*R_Lt&%aU?AkQ+a*N&q` zSz6D=Ot~nZf%E-cvi;_$N&ezYcy$^*NwSU^{b(me&WJ3EFhRV88QnR67ovh zb>{;MdfvKd;-)E+U;O+2Kisg}{;jU>U8O1)%|3VF&t^4@ykh(I4GW*U{!iFKRj&<5qQ(Be{^wOMoqxgc3)ND4+8w`m?De~TeqqZM z<0t>+hIhX*ylbE8=1brCi=!VG^xRxoh5bEq-90y5c5ThD`NPk_MmQpv*xOw**((8U zP-7k(9qKxQwrYmkNJ+(YV)q!FFo2H|Y=IbFzfs73!i^M+*Y=HYSm}k>MY?&R+lkwX zkaYz(sojlO!X zKdCqjw13|)?5#_l*k`Yqwq-&{Ki>9i5zcsg{@SOuefE}K)y)s6VAqho<39VW3>if@ z2j;58O;QhJ%<1n(UF9gHN9*3`k48*2^wENaMQ2{$p~_G{Fn)MDK` zW)g0*M#rhh&<|tU;}VA)fC|I{km53KdO$_(>@tLlT}5rPuNpFT<~RS0PY6A8<;$O) zerx@Qi|y2Po9?*l{QD-3yG#AGwK;WN7YI%Dxj*~jt>$)@X`;(%s^I3H#vH+T_NHp1pfl^e8SaHMJbeZKZ3a6`npd zqXOj@ ztQf1B5BNNbp5J$9=W>tln6YE}6?vDJ&d4jx>)!MH(GxB$xwxpJv}<8bVQ{kjm5!4W zXizxh89AU0Zg{W8e`L|K2g^Ts=;$Om^hs`(eEwte;0u+2Rx9{mMs z&05GB&wy1}*7V>;tjHY$RP^s+eT@9Q$vLeWTe0)^V)5bMy|@fA0j+}^V)mvpzMzK} z@$Z#f*?>J@@lgvg+Zmem3@-g*t4FS-pk1I$i1-}9$*1o}ip09{=Iyd#H8vpS)dt(w zIcp$ybhY>!bWDARH?BF|qw`AZH0(S2rR{I1QTB6ZtfTN1y`P+dihN~`{TAMkQ(am7 z3~WNDAHzF$d9fOE-a!PchtK#9ENA*CISUtgV~oAudBYni>*+t?jdQ)B*c(lDWJTwS z)@A~9oUI~}l85F|=Tu26UzMi6o!?3}5W( zF|`srZ;=*6^rkOP-=le-QqVdF^pV^ny`g*Lw@#b?Z>{nFC2M9WhpdQ= zJDpnkqOG$Fw#MbL*1;;=ScX=@%bNB~P*Z1luGh4rzJ-vQ!qW?YA?O@#t(F)j)?-EC z)`pyZ960RsD&TP92;#`o`|xBe@f_kfK0lY~@l2n`^aSEW;tl-PO5%;gRm7W!tBDWt zyIYyV!~FU-;v>Xn;xCEYiI4Kl7UE;X9mHP|cM_i_K0|z#xQn=(_#DgjJaG?kFY#rT zXCJ@v3e&GLy`SmVm_EQ94iOI%j}VU%j}hM`9w(k4zDGPs{E*-JnD{C2bK>`c%0u)K z6N$<4D`gQ=h-t(O{xyr(m6%5?l-^Xu#Bx6AMXV%N5vz%Ph<*8HKj{S(CJvVRDoTPH zCbd?S0Yw>56ZqE)h|`Ie5|{AJYfm4?`%8&G=aUuu$_;$7l6WI=HE|7bE#JJIZ?0qd z4&q(J`-t0E>lS|Nai-bJ>L7D?lh4@~>RqOP&v)4J>I0_#O#C0>*ZkJs`R4a*fmWg| zs3ROI@MrRlK2hG$CrKIf3+1=^bUvTM^p*VULL#iK`0H{$UqxI^TtmE#_!N;+s=vwf z5&rfB@efiL{XM4NXZj@5A29t9-~WW^&xqgf$=`|J3L0_pzF{&Q&vXLQlpiC&C+U3B zg_upu;q$J>Z2(=|-@WvTiR!^C=GBXJOM2yrBFG_i?DUNXo?#sof@NSsWZLYziqZyM}PV}|sp zF_So(IEQ#SaV~M5NQJS0xRAIG(qfWPO){!UMm05IR1>zzRG^F_lZqnes9s;LR1nwl`GsR^T+S_@Ga)zpMhO-&fp)I?TIO~#t338R{t zFsi8uqnfZc3ksu}nlP%V38R{tFsi8uqnes9s;LR1nwl`GsR^T+nlP$~_>Pi;Fsi8u zqnetGVp9`FH8o*WQxirtHDOd!6Gk;P8P}#JjB0AasHP^2YHGr$rY4MPYQm@{85O!7 z)555x5=J%2sHPG|HI*=`sf1BYC5&n+VN_EIqnb(>)g+^uN*L8t!lUql`yKQgi%c;jA|-jR8t9~no1beRKlpH5=J$ZFsiA9QB5U`YARt=QwgJ* zioHxmHI*=`sn{1}R8t9~nu;w?Mm5!+`9v7iRKlpH5=J$ZFsiA9QB5U`YLZb+GO9^N zHOZ(Z8Pz1Cn))2cM;AslbzxLf7e+P7sHQHAYU;wMrY?+X>cXg|E{tmGlu|OPsSBf; zx-hD#3!|E3R8tp5HFaTBQx`@xbzxLf7e+O8VN_EWMm2R|R8#*}kc?`QQB6Y_)ii`r zO+y&fB%_*!Fsf+?qnd^=s%Z$Lnuai{X$Yg5WK`1-Ml}s#RMQYfH4R}@(-1~A4PjK% z5Joi(VN}x)Ml}s#RMQYfH4R}@(-1~A4PjK%5Joi(VN}x)Ml}s#RMQYfH4R}@(-1~A z4PjK%5Joi(VN}x)Ml}s#RMQYfH4R}@(-1~A4PjK%ATNHU&6W0?r5bq)0OWa7jpE+zIK0w?|e31FP z&UX$H-ypt8{DAlo@e|@_f+~sVCkBYA#6l^pDkk=j{8dk41+h0VNUR}BPbDkqsbs}^ zso~6LCi*d1T|r#H+^*z13;EZpnO;m>#e5#%I}h@&%}noN`Z?nBe6p9hy>xmCau$A0 zR`2t#A2T1PUf(m_O0)%ajVLozvVMWQuU{lz)GufHI>}A{In&qkog0WZ^2tran~Ap& z*AgFe66GC(y=llyk!-L}jWl`F$dK z_p9fLuMmX^{Yn_muRkhJ(DIUgn(1el-ox}>Nu$8%qAAcrp2*oX)-n~O~$5$ zk}Lqq*t9q{Esjl#W7Fc;v@{u;mL_A<(qwE}nv6|Lld)-OGBzzu#-^po*t9emo0cYH z)6!&YTAGYaOOvr_X)-n~O~$6B$=I|s8Jm_SW7E<^hAoawT#aH{#-^oYY+7hBl$K-D zQZhCzj!jF+*tC?4O-sqxw3LiZOUc-@I5sVgO^ajGQZhCzj!o=^Fi#nqmXfh)DH)rV zlCfzi8Jm`pv1ut8o0gKXX(<_-mXfh)p{L}Dj7>|4JX;)_7RRQgWNcdKJ9$&arln+T zT1uqX;@Grw8JiZzrp2*oaco+;j7^JU)6!*ZTDpu)OP8@}=`uDgUB;%R%hn{?9GjLQW79HZY+8nl zP0Nt6X&Ev$EknkpWysjH3>ll2A!E}rWNcc7j7`gsv1xH^S{$1e$EIb-*t858o0cJC z(=udiT84~G%aE~Y88S94jFbhirMrnHu^4X_V$=x20tpo9D#X|kl(AEYQS&%3Oq3a| z5Ti!^dN*-BaRc!l;=RO;L>V207#;HdLE;<4H;JP26k>D;iq2Dr(E-Hh5cCrR#8je; z4#bQj_LcHb@(U^Xg^+yt)%SefO0)$jfrYwPex-}PR0ui5o9Ge2QN%ICvx(;r&n2Em z{26f)@qFS`qLYubb0OQgknLQ^b}rJA@%19jA{OHPBDCi|Aa3dc!^Ap1uV=b}C|Yz8 z)>DEv5@m!IVLc@%8g&skM{;46p6wqGeJsAF{Py#<5!-Dv=n3f3W~H8WBdw=v=n3f z3W~H8WBdwAJ|&cj5=uo0*6H#@q@sjUQ9`LG(L^drFhhL>l>REgDqEh2RFqIEO0de7 zv`9q>rJ{sVQ9`LG!P-`yi&T_QDoQ97C6tO1N<|5!qJ&aWqWBd`MG4l1@+*;w5{xE6 zk%|)VIgnCOLa8XBRFvo<6(y945=uo0Rul3>q@o1t2tkpG6098rMJh_LUJw+iD8U** zP^6*+tc9;oDoQY#BrQ@=3JrWIG;z^?%Fud(;$N+dZCJ+kDP#MTv3<(eK4om5GPX|{ zTc(UHQ^uAlV@=CY7vzSz2ufYbSeG)^rHpkcV_nKvmonC+jCCnvUCLOOGS;Pxbtz+A zDk#Ynl;jFZas_#{f|6W8Nv@zIS5T5GD9II+B_v$ZVPZY{w36*m$#$q@J5;hAD%lQ| zY==s=LnX8?$zc=m0pe!jgUm<9dnH?`lC4z9R;pwxRkD>T*-DjcrAoF^C0nVItyIZY zs>Db^J{T#2JtTi?>S$sVa>k5sZpDxDsIZXmf`OuU3Ri+CyV zGU64)c~TyXGQow!btq4e>;$(jVEZ6PR*>uzBs&GkPC>F$kn9vBI|VuVf*gH8j=msA zUy!3O$k7)hI|VrggB*iFj=><=DM)q-lAVHNry$uWNOlU6oq}YiAjfQw>=YzB1<6i9 zvQv=k6eK$Z$xcCz=paXQkRv+C5gp`+4st{XIiiCc(Ls*rAV+kNBRa?t9ps1(lAVHN zry$uWNOr2$lI?$CKi(o1LQkt^D^;_7s?k33WF=8F+iJ9rpy;>NXdl6siLVg%3$pIj zta~-`u%h77&YwCB!mfIk6Y9l2}Eo2G+3mYuNiW?EM<{ zehquShP_|IdeyM^YgoG)_I?d}zlObE!``o9@7J*RYuNiW?EM<{ehquShP_|I-mhWr z*Rc0%*!wkXff}|z4O^gwy?f?pk?h(?a5T~+iVyh3Krvz^#img6`o)Q#WeTbYKA}5E)$suxb zh@2cECx^(%A=vZf{fAlpZNx{2&BR|4w-aT(7J@xr@G;^J;;)E1iL!PJ!JaSpEO8fc zH&NE1A=vW;_Yh?@7t&r}?Zm1df>mEy@)f3EWqLo;uQ4riLy zc792VmKcJyU(!d3$B43j7lO54P^`EiSo;M}5=E;F!R9YdM7IpV@-Jyw(S%_Cm-J^$ zf6nyxQgY=X`iO}{S@ncevb@8&DWoi>MI#NV6sAQl4XHGyGx)14Vpov?><$RZ?m&pU z10n1V$hTy?hm?%>kdpBpQZn8{*c}j*IXa~3MM6}6Vk1%Z2twE+K&xVpKu~745OxaW zxmaaG>J*=}3F^B1N;iluqG(njYE~gw+VL%nNBkC+cI3txFa&$MJee+k#W+P8)@l3| zqgB$mOpA3o1naas7wdEgBUiAPPsBPMGD?{)W4Z^^Vx0~dJ((8kbO_dI$)S?z-b`09 z9b{Up(;--=<(V_kE{_wMibMrQV4N24;dWA zoYO-FM=@vi5bW6UD_J{)V9k~^$Feb*X^v%M3e&Qh2*JKB?}!!=f|Xm+P8-f(OU@+D zCe9&>Ei?oxx8QvKN-W#`z+K0I7O@bzK|k=hAbi9CZzQfF-b7qYe3|$Palas#83^u0 zZs>8riSh(}D%hQvM=T%~5le`;>5X^FiMXGObS1HhSPiU2IX40=qOfZ%*!6K>G5;#s zS}oXBP;|3e@al2kFrvt9tu}&)TZTx(PYLiG;y6A(m+A3LpU3nBqST@mZ6e=VNxYG` zig*)oHE|8!yqmb5xPf>N@m}Ia;#Pj;Vd6I8BgAInFNxcUj}lvmj}dnee?{C$e3~dW z(poU5;4b2B;&c4s^Ta*Gy~LMU=6!reY_PT136g7NTIj1fuaGM&eCKGOwE z7ZF95sD;KNZyFimlzb$gjAD8;(_@%!V)|^RIljPnk`Koh7*En1Utl~*vz5Sj zlAgq0iS|;9)s1{bw3k|}aU?yBX|^F4Po6t%ID>zkNyPbIJfB0noH!R4CVPa*9%1Tc zVX{Y<>=A};ChrJ)gvlOZvPYQg5r$TF9M7ef!{X@?DC`j?dxW8t$#bb)7+RU6rFLOx zWrD&UVX{Y<>=6c|K8`2C9%1NRg2EnQ=w0%rutykrm!Pmm7{M;PZQB$65r)Pk&xJk0(6}Tm z>=A~>B`E9>#`y|CVUI90E{M;L1y{0jOK$_#x;(!w5L=u7fM*dt8#2xHYFX=7n=gvlOZvPYQg5r)1be--u!lRd&@k1*LIjNN>BF6=7n=gvlOZvPT&DlH?%l5hi{M;Q8& zJQwx|lRd(Qut(Ss_6XxtfS|BP7^eaRg+1!P632nkN_AifLE+3gu9fPzR;uG#sg7%< zIbT;l<65JR7OFa~8tS-SsN+hZjutA!m6fkR8U<5{!bNq| zoa<;cs-wCmMwF8P_25Q9nGx#2jr)LdeykqcD9`2mSUtE=(sF*R z9^CjiP|lClYf_SWFr<8OCGke0oUW+{Lkh|nn|ii(JzKk;tzFO7u4il4v$gBl+Vx;a z`PRe4ZNx{2&BR|4w-X;F$}WFB7*cQt@mIv1#HWd}t5^?)6qLQudN8D*oHVQFNwaz| zq@?AfSv?q1%Dj*7yu$RWOz&ss zXQBKhx|HcMrh715&U8GvzBa zO!p=BBZi6f#75#EBJ0kPUJuTc97ajYfHNgMhUq4z&u01@BF7yWpx)rPBM;PrGv!yZ z#;gZtN?P>NdT^$sg&FF>nUdzH1ZPTG)|mC+Oi4R!C}&ga!I^?`lByn@DJUnY>cN@& zv<9%pS3uFM8o(ZcHAK;K8mQ+qP|sJJUn92%%IG*DY;;OyVP zdB1@(ego(F2F~UUoW~m=pZE$SQ&6<@2F{)hoP8QN?=*16Y2aMbz`3S@vrGf$mj=!( z4V+UNIGZ$Z9%?(tY$TrI^EN@|KU8P_T)z*)?T0dL7*3S}buTddZ~lX`868Hc?+~qQ2TheYJ`DY7_O< zChDtA)K{CRuQpL%ZKA%~L~XMPmOaT?G|VRImQBK90|E9}2xW zj=leXNP7SHIIp|Tcb<8;EEh^vh;oCN-WR)&PM)^LbqfeLy}Z0H#1ggzdK-5V8l_E~ z+w0qO*UidlShJK;^s_3V?WXz_#nNP{B)hW5FDEOzMjlD7JRJ=}Q50dX;@^e3wrK?m zQXOVS&y4Qp^X@;N*Y|bw%yZ89e9!ru?>W!WIS=9ehw%PGc>f{1{}A4P2=70H_aDOh z58?fX@cu)1{~_N0&=22__xF;^UUJz>E_=ykFS+a`m%Ze&mt6Le%U*KXOD=oKWiPqx zC6~SAvX@-;l1oNy?4d+PZOrKCF+(fLB;1NJw4w|%vJ7o0LtDzwmNLwbGR%)M%#Sk6 zk21`UGR%)Mw6_fHEkk?D(B3k%w+!tqLwn26-ZHee4DBsLd&@8b$}soIFzdJXK48uT7HI>pP}VvX!#jheukF+2>SL2 z`t}I=_K0fqj>Jc46(6Nl=oEFu03TKCYV@k$N2&Wq6%{B220p6T;6&n4e)TB7dX!&1 z%C8>fSC8_mNBPyG{OVDD)k=G7rM~nO53ND{ZEgHq%O*X{F7y(q>v|Gp)3ZR@z1@ zZKIX8(Mo%0#rv)Jt`*<4;=5LS*NX32@m(vvYsGi1_^uV-wc@*0eAkNaTJc>gzH7yI zt@y4L-#rG~z7tuB1KZj$Coen(+mFHaW3c@gY(ECuA7@l;m$tMe+NCW<&q3N1MHsz5 ztv&HxY`4btg!iYlC%iwcJ>mUn?FsKsYiFj=&P<`5nL;}=g?45N?aUO~nJKjEyQH6Y z9Ny0Ctex3eJF~NPW@qih&+W|4+L@iTE7H*O#KY}@BjI*M8b*KbYuDFIqxYw^2i`T^ zuCJL!t5&T4(7YbVlcSFGZ6$9V0ERg4+X z5nVg6Tsu)*JMmjPkz0HCwBH}Lf!+t$uCJDDI*;BwcffN8Ja@oz2RwJcb4S8FcffN8 zJa@oz2RwJca|b+kz;g#YcffN8Ja@oz2RwJca|b+kz;g#YcffN8Ja@oz2RwJca|b+k zz;g#Y>(G0b9G*MixdWa%;JE{yJK(tko;%>V1D-qJxdWa%;JE{yJK(tko;%>V1D-qJ zxdWa%h@3m%xdWa%;JE{yJK(tko;yPG+yT#>@Z1T{o$%ZV&z>W2WZ{sg6A%H?tdr;cfoTPJa@r!7d&^ta~C{!!E+ZpcfoTPJa@r!7d&^ta~C{!!E+ZpcfoTPJa@r! z7d&^ta~C{!!E+ZpcfoTPJa@r!7d&^ta~C{!!E+ZpcfoTPJa@r!7d&^ta~C{!!E+Zp zcfoTPJa@r!7d&^ta~C{!!E+Zpcf)fxJa^NcyWzPTp1a|>8=kx2xtsRf4bR>1+zrp& z@Z1g0-SFHE&)x9c4bR>1+zrp&@Z1g0-SFHE&)x9c4bR>1+zrp&@Z1g0-SFHE&)x9c z4bR>1+zrp&@Z1g0-SFHE&)x9c4bR>1+zrp&@Z1g0-SFHE&)x9c4bR>1+zrp&@Z1g0 z-SFHE&)x9c4bR>1+zrn?@Z1B>J@DKE&pq(m1J6D1+yl=&@Z1B>J@DKE&pq(m1J6D1 z+yl=&@Z1B>J@DKE&pq(m1J6D1+yl=&@Z1B>J@DKE&pq(m1J6D1+yl=&@Z1B>J@DKE z&pq(m1J6D1+yl=&@Z1B>J@DKE&pq(m1J6D1+yl=&@Z1B>J@DKE&pq(m1J6D1+yl=& z@Z1B>J@DKE&tG6?X)g@-!f-F#_QGv19QMLtFC6y5VJ{r^!eK8Q_QGB-?DfK4FYNWg zUN7wR!d@@z^}=2+?DfK4FYNWgPcL=vrS84dy_dT8Quki!-b>wkse3PV@1^d&)V-Iw z_fq#SQpZ07p9B9Kd_Lj$`T2xnv*)$yyC(E*9sVDkL^VY{s zxjuHv^}$;oy!F9bAH4O!TOYjj!CN1^^=a)?Z(yffAH4O!Tc7%x-Vbkm@YV-!eel)? zZ+-CA$4xZ{~cxZ{~cxZ{~cxZ{~cxZ{~cxZ{~cxZ`icpHGX0eBmLw*hz?fVTm78-TX~cpHGX0eBmL zw*hz?fVTm78-TX~cpHGX0eBmLw*hz?fVTm78-TX~cpHGX0eBmLw*hz?fVTm78-TX~ zcpHGX0eBmLw*hz?fVTm78-TX~cpHGX0eBmLw*hz?fVTm78-TX~cpHGX0eBmLw?TLt zgttL>8-%w(cpHSbL3kU4w?TLtgttL>8-%w(cpHSbL3kU4w?TLtgttL>8-%w(cpHSb zL3kU4w?TLtgttL>8-%w(cpHSbL3kU4w?TLtgttL>8-%w(cpHSbL3kU4w?TLtgttL> z8-%w(cpHSbL3kU4w?TLtgttL>8-%wZcpHMZA$S{tw;^~Ng0~@f8-lkXcpHMZA$S{t zw;^~Ng0~@f8-lkXcpHMZA$S{tw;^~Ng0~@f8-lkXcpHMZA$S{tw;^~Ng0~@f8-lkX zcpHMZA$S{tw;^~Ng0~@f8-lkXcpHMZA$S{tw;^~Ng0~@f8-lkXcpHMZA$S{tH~l}Z zMk4)}9_aB@yX~ZV+6^Q<9EP`Hc+t4a3_oybZ(KFuV=J+c3Nh!`m>t z4a3_oybZ(KFuV=J+c3Nh!`m>t4a3_oybZ(KFuV=J+c3Nh!`m>t4a3_oybZ(KFuV=J z+c3Nh!`m>t4a3_oybZ(KFuV=J+c3Nh!`m>t4a3`u;%zPQqIfgjCr(}zC&mu26YK)J z!5(lPEPzF$YA00r^&XYq=p8sOs{F>E2fgF#MU~(9cJO}i9pJk_?*w~MbA)n^P|gv`IYK!{DCY>}9HE>elyih~j!@1K$~j6o zM=9qhPw1q@g9k+m@#^f zL{`igy+)S@}k;t+~BFi3$EPEuf?2*W_M^%}$ zdDFJ{NMz+x+ukFQWmc8d3b}3Xk;rPD+_v{fWVKpu+j}IkS~IupJrY^1nH#-FBC9oX zqxVQ;wPtSg9*JzidnB^#k;pPT%j%npQ~nP84tNvv9*Hcc_hbX_k;t+~A{%&*M3#A6 zHt-&aEVH?6;5`yq=5*P>dnB^V?6QIPNMr-=k;n$#BascfMK(SdM zy+T$NcMEb zmOT>L&@1X$_DEzye?iNJ-XoC>y+BFij0%N~g=dnB@<_ef+z?~%x| zMR*yF0dQy0q4O2STr)? zzr=|D5+nXgL5tJ*ud$c-ud$cpeWl3DM*r8?OY*Go7s0oK_k-^M-v#~>_-^n$;4cgR zLhDrPLVt1oi{!roy-VS1q<@X{uaW+>q|?VpA0vH?^fA)MNgpSDob++hCrF*OZF2~8`IJq1rm*eDeoLr8R%W-l!PAoa-3X_lgn{(IZiIe$t6cF zIdaL7OO9M}vJDE~m-mG`XB6m(%2Onp{qk%V}~sO)jU& z2#|fH|np(pAb%y`!u;vllwHePm}vJxlfb(G`UZc`!u;vllwHe zPm}vJxlfb(G`UZc`y5}2=lD`Qrzq=0Vop(((NWeMUyA26ekp}7#d8|PP9)AzwsVy2 z9A!I4+0Ie6bCm5IWjjaN&QZ2=lF%wr^6lZ&J2zQnqhW zHlMxyj>I=9+czoOH!0gUDciit)|Qx8*^G`4=ZO#JS+}1jN}MN3oY&mY&-gpkyyk{R ze}|eUikoN6d7d@rdDfigS#zFe&3T?R=XuQv{k*?l&l4TZ6Bo@B5zQ0f%oE$p6V=SK z@;pz(GEb~BPn0rGd@`@Oq{^eYq|x86=L3Jgp4VK`_@HyqoYCq2em$=_qfm23{Z;Jg zS7OiTh+>`*d7iO&o>6$7@pqo_cb<`Vo-ucx(RQA3cAgP-p0RbFQFT7__v`u4->>I2 zXEgfzwNA&>XreRQjQH+6zAM0g0saf{Ux5Dt{1@QA0RIK}FTj5R{tNJ5fd2yg7vR4D z{{{Fjz<&Y$3-Din{{s9M;J*O>1^6$(e*yjr@Lz!c0{j=?zX1OQ_%FbJ0saf{Ux5Dt z{1@QA0RIK}FTj5R{tNJ5fd2yg7vR4D{{{Fjz<&Y$3-Din{{s9M;J*O>1^6$(e*yjr z@Lz!cZ^8e!;Qw3j|1J10!haF|i|}8B|04Vs;lBv~Mffkme-ZwR@Lz=gBK#NOzX<(U+FT#Hj{)_Nmg#RM^7vaAM|3&yO!haF|i|}8B|04Vs z;lBv~Mffkme-ZwR@Lz=gBK#NOzX<(U+FT#Hj{)_Nm zg#QKjUx5Dw_+Nnk5}cRdyad}N*e=0p306z6T7uOQtd?N41gjQV50;ZCD-6FMHq;`wcZjst8QoBWJw@B?4sof&ATcmc2)NYa5EmFHh zYPU%37OCALwOgcii_~tB+AUJMMQXQ5?G~xsBDGtjc8k<*k=iX%yCrJ3MD3QS-4eB1 zqIOHvZi(70QM)B-w?yrhsNE8^TcUPL)NYB|Em6BAYPUq~mZ;qlwOgWgOVnXXrgqEJZkgIGQ@dqqw@mF;h!9qY5LPssTT84Yy!N}2@Y?T+^v&pR zf-6!v+g|%!(Jap?{wBDhRUMzoo8do@n&9)D_JW zjlT%`Tk1;aZ-OhD?;HJ1a7A-`qrauDXkKshH^CL@nBFfPGx}TV3TwZw6J@-ciwb;Va>!heVT@AcmTuO~hT{vP-t@Cp8U8~g9I^Za$~>Sj88s_+k}btm2DRe6flzR`JCuzF5Tx)F!7a5^0GD2M>y8fT!rPJlr5_HZn=#`c=@>(OWHS$^` zuQl>oBd;~`S|hJD@>(OWHS$^`uQl>oBd<5e>k@fgBCku->k@fgBCkv2b&0$#k=G^i zxE|J$I^14i3SIFxMd0io|E97;BysnVf74o`5URTKL3VB^2 zuPfwrg}kni*A?=*LS9$M>neF&Bd=@Zb&b5Pk=Hfyx<+2t$m<$;T_dk+Sa{DjH;JW^)jkn zM%BxxdKpzOqv~Z;y^N}tQS~yaUPjf+sCpSyFQe*ZRK1L=CgZ{sj3Om3n>;SK@1H8fx@JjfA{~G##?kem6udoBWqSaKV z`2SWa>;SKXzlTk&!;fJ5|5hsO0I!7pf7?pv|I@Fq1H8fx@G87j;jId9Rd&u-;jId9 zRd}nyTNU1_@K%MlD!f(UtqN~dc&ox&72c}wR)x1Jyj9_?3U5_-tHN6q-m36cg|}+L zyj9_?3U5_-s|Mz+3U5_-tHN6q-m36cg|{laRpG4)Z&i4!!dn&Is-bzS!dsP{^Hq4O z!dn&Is_<5Yw=MOu#}ZrWWyZ9AyG761qGxTtM&mpEeoocf3U*@qRJ|>oX>9a=2ySWg^pieSZ%d=6 z(Yqd=|`m3}^TuF>tW(;7al;nNyEt>M!e zKCR)?8a}Pz(;7al;nNyEt>M!eKCR)?8a}Pz(;7al;nNyEt>M!eKCLNkIj@p__T&kYxuN=Piy$JhEHqww1!V>__P+-r!{<9!>2WTTEnL` zd|Jb&HGEpbr!_@BDjPnn;nNyEt>M!eKCR)?8a}Pz(;7al;nNyEt>M!eKCR)?8a}Pz z(;7al;nNyEt>M#}PEl5wb&9glKCOlJX-$!mc*CbPd|Feaw3g6qXKHWT_;eeeZsXH! ze5yM_dB**88=r3D(`|gZjZe4n={7#y#;4o(bQ_;;uHa^|Pr`z~+8=r3D(`|gZjZe4n z={7#y#;4o(bQ_;;uHa^|Pr`z~+8=r3D(`|gZjZe4n={7#ywoi32nf~8UsQ+3LY9=E5 z1yC~)*_w$6H4_nPCL+{KM5vjFP%{zX-`n;~M5zD2(`Bq`Cqn7DP#P%Ie=`Z+3#y&S zRyz?&&xO)+q4ZoRJr_#Ph5Dv1)Hi*hzUd1GL4DJgJq*6c8xDigbEQkqh3fl4^?jkf zp$ql(T&VBnLVX7p>XZPXzI_XC8r@EWI)g&k2)-4Ro-2jAxShz}0ZPwhtM3b?=R)bZ zP^`GyuKLmal+zV#F zM?lRc^o+jP3iYj4$lKgbWdA>)^jx<3zEFK%sJ<^$-xsRy3#I2m>ABGDB*dp7J`M3{ zh)+X&8oKZ68T&NEry)KK@o9)pLwp+I(-5DA_%y_)q5HmmYoCVh`$GFPbl(@+ry)KK z@o9)pLwp+I(-5DA_%y_)AwCW9X^2lld>Xp%2ci4E&^`_EX^2ll_kE?Z1@5TAzlG{mQ&`+gAO)6jiiwtX79?+fkI(0yNM zpN9A}#HS%X4e@E{zOTR9ry)KK@o9)pLwp+I(-5DA`1Hr*({)|5sCj7cV=AptU#o+A z68a`3)Hf-iS-MA_JulR%j!-KtLapiuwW=f3s*X^rIzp}L2s=To>d5W}dqC~9|&Nf2sPN2paDp;mQ-T1gPz4{B9MwpMk7TGbK08`P?fY^~}DwW=f3s*X^rI>Ilj zI) z0B;TO)&Oq}@YVot4PJo>8sMz~-WuSo!7DIIH*XE_)&Oq}@aB6=&IgU~)(CHn@YV=# zjquh8Z;kNQ2ycz>)(CHn@YV=#jquh8Z;kNQ2ycz>)(CHn@YV=#jquh8Z;kNQ2ycz> z)(CHn@YV=#jquh8Z;kNQ2ycz>)(CHn@YV=#jquh8Z;kNQ2ycz>)(CHn@YV=#jquh8 zZ;kNQ2ycz>)(CHn@YV=#Z}mz@a4#eHy^P@Z`osXG{1)gD=U&f4g&&ZA@AYg{_!00g zz^{R0;5aw|9s!SnUk4|_W8iTx2Tp;fz|-J2z%$@9cpm%~xB&hY_}Ad8;A`OP;NO53 z!8Py_sJXw&uQ{yn1~vLy@H^mljlsY6UxS|le;WK55N3R0fc^?6L@f6bvE1tu1A==! z^9}Cxi2=fY1O5V-1|!gs%)Q|ba3`o!!j$6J=3edKGJ5pC*K=p#1EAI%WNY`D@Harp z6Mg{ucR;N<=&$%F#7CYxE5+J%Z}=#vH3!)r2VL4Gyx)ZPoA7=U-fv3S`%QSi3GX-I z{U*HM)!uLNc@aW;zscuC2<`nQpBEvt_nW+4A+-0Kyj~%+_nW+4A+-0Kyj~%+ z_nW+4A+-0Kd|rgm-f!}G5kh;v$txH_d%wvm7(#o$$txH_d%wvm7(#o$$txH_d%p?q zH~G8>+4g>u&x;6}@O~5CZwl=Froi5B@_7+Ld%p?qH{tyzyx)ZPoA7=U-fzPDO+GIo zXbSE9rqJGR((XQ^z2D^XB82vS6W(va`%QSi3GX-I{U)!R=ox#z3GX*~kM|`93{~i?6P$RyRd-$E$kw;&R|!1i6?aiyX-~mzs6p|)*0+d zU&ek7TW7EXHXP3jP@Qli)pmMtw)mGfTQpJ;-(=={kd5_FJ)a2D|Kg`Bf9BUDHa@9%-S@U>EAn zYoT_}3blJysNJ(d?Vc5C_pI<&!C&K@I)hy)I)h!PGuVYXgI!4bRG$@x%(L!O&vm-a zU>9!j+nO<{GuVY8_#>e9ek(?Q{>AxyYldZn5zbaa@ ztuxq#I)h!PGuVYXgI)M`P-n2qz8_m>u*=pN>_VNvE_@fZ&S00VGuVYXgI)M;Y@NX_ zTW7Efbq2doXRr%(2D?yaunTntyHIDa3v~v&P-n0Ubq2feH^Kklx=TZx@QOk2rlrxeG@6!1)6!^KS|ebao^hK=qiJb0Esdt7H5xkInwHj>Xxo~WPFT~@ zXj&RgOQUING%by$rO~uBnwCb>(r8**vk$+=nwHk=!)Q(8_Aa3{joZ6~)--PK5?a&J z8j=0HH7$*%rO~vsMr5a0)6!^K8cj>1X=#nf{*^T?ji#m1v^1KQM$^)0T3RErpRuN; z(X=$0miE08O0lM;(X_PYN`A(gmPXUk8oO;<)6yEjZClgQ8poYtO-pMuw{1;JYfQIo zO-pNJw{1;JqiJb0Esdt7HL^S1nwCb>(r8*5P21X=&x7mWigN z(X=$0mPXUkXj&RgOZzLcp0uW=(X=$0mPXUkXj&Rg1X=yYqji#m1v^1KQM$^)YbL3ex zEv;zBwlyt{rlrxev?3p;Thr2LS{hADD++SDH7$*%rO~uBnwCb>(r8*5O-rL`X*4a3 zrlrxeG%I6iG>u#5^fqf+8cj9;|4k*y%rA8n@I5t!ZgAEsdt7(X@1EO-qN?w6y-8#b`}Sht{-oXiZCp*0i*5Ob+fx)9y#p z?nl$^N7EuSEke^GG%Z5YA~Y=`PK(gA2u+L7vR(;_r2LenBNEke^GG%Z5YA~Y>R(;_r2LenBN zEke^GG%Z5YA~Y>R(;_r2LenBNEke^GG%Z5YA~Y>R(;_r2LenBNEke^GG%Z5YA~Y>R z(;_r2LenBNEke^GG%Z5YA~Y>R(;_r2B2J6YvCP zYr#9ETE_dd#_~?7gs}tEe}BvF0`=eDvU@=N_qS~Q_qS00{T;khtwku0>pk+g@%Nn~ zpBjH2{I}pQf^P@!2le0IdgfiA{`*_D{!3k`|56v~ztn>rlye8=+(9{aP|h8cbBEN- z&$ygBq-I8!bBC0{=yL9$oI5Dz4$8TMa_*pP8C}jDYGX#1a|h+z zp%!J^<=jCzcTmoqlyfKL+(|iiQqG-}b0_88NjY~?&YhHVC*|BpId@Xdos@GY<=ja* zcT&!slyfKL+(|iiQqG-}b0_88NjY~?&YhHVC*|BpId@XdU6gYd<=jO%cTvt=lyev5 z+(kKeQO;eIa~I{@MLBm-&Rvvq7vPDZ2s?mHQUl+(8;t_8kD z(dhQ+TNI6MpT0%WXutawMWg-hTNI6MpT0%WXutawMWfrN?-^VZx*z$TL8rK!zC+OH za{3NIqs!?#1dT4IZx1xOoW2cENI8AqpWa3}eczwbDW~uIv+Z*FzCWYO>HGeSE~oGN zGrF9<@6YIRHmjUEQ$yu6dNrw8r9B}`^H+cSZdR0H^q1~t#V2b*udFsFyyo1T_-)W% z9GVrM7(WbpWxF}C7yBdFx-~{A4})gAS)4ep4zLr{tuac`tuaE~8YArG$v#lG#wcX~ z)U7eHhrnUbtK7}3b2hWi*~~g;GwYnqtaCQA&e_a5XEW=Z&8%}av(DMfI%hNMoXxCr zHnYyz%sOW?>zvK3b2cjiaqf9=2Al*39cJ^Wj*iZ?6v zu!j@6?-_{>*URfJ^U+wD`{5jVfSx8!EU9=5&G-mKWew%6O66?@oj0^Ks3 z6?+)n_L>!M7~S@o1Gl|q#T&+71zXGvYqrg-*)}V#@Ly?HiYsKR{i}Vd{Tm-4MYqPt zcAVd=$idHeg}hmjgKYH<{gu_-X7v%<|Hgk+PqFQF{$}+T+qyMI=(W>k^%~m|wr-7) z?UjdS^&s0juyt#U?48)}#`YJ1X7wqjd)>8JJ7R+5_)IrweIv^A@(rOUgx*EK z-Jrp*vFiS7@|{vxd-pwwCj#Hcr_(?6q|utQTbg6rUf9jdXty-SwmIA_&C!WJ(j4Q1 z;BSNGYPU4!yig~N2zBCtP$!KDb>e|gCyfYo8;el4u?W4cZB2&P8t#F zq!FP`8WDOuXE$@7-OPPZB3b$H5$^lSY(s3e-s>vcCbI0jELT#-j8*sM}a%>oyjlP8t!O=NX+eB3mbo2zAnk zZ~?nSjdaq8QoNG0Tgqd53Hw!SujK5O@;Lo<@NdA2;2NltM)X&mG$PbVBSNoE@0Riy zzYXf75!qgy-mO`W(W}$DHS00zHkM$wl*g#sScE!hM0k(?s#%ZT$*gC$l*e`>=|7GA zR_vd_zL#I=q!B&iUcXz)W7KUdLfyt9d^f0*Mr7-x5#g_b?uolK$1%D;`i4hwB`tD} zv^VBByQM`=(QPb&?|;-YYRSe&{2qZB3b z`$65tB3mbo2z48aP`9xJ??a2;hZgxJ%@azo7QGLJc^_KzKD6k4XpwI)JP|yA7CnF# zJ;0N`HBYA!qeZ?oZ%wwf$hYPh&5v)*Gg^xtNVskI);#~!TI5^vjON6*=GitUzBSLb zwaB;T*>!7WwWxqqWF)=NYX^4)nxYmx8HGg^y$cb?H&W9O;1mvMGv4wzD-Zhphdn-&$j!KZ`0G0XpwKzvu!Q% zZF;t?MZQhXwzbH&={dz(^Z;7q+w^Q(i+r1&ZEKNl)3a?Y@@;yytwp{~&$hM5x9Qoo z7WpzD>_)E%I%8#{b}2qeZ?=&$hM5x9Qoo7Wp`8GYHwaB;W8LdUW zO>bTJPSCTb_e(vDp3C?)J)`F`zD>{Qxr}eqGkPxL+w_c{9r!jqqh|!ZP0#4q-nZ!) z9nbqVJ)>jw_p5yx9iP9SSlYMg$tJ?|ZF;sHRr)qPqvJ{6re|~v>D%;-jvIZOp3$+Q zZ_`^3xc5rv+Hvod(6#$@YRA1-vRymwy%M^1+})l z-5zSU2jA_Xc6;#M9(=b4-|eAxd+^;JYPSd9?V)yi@ZBD2w+G+tp>})l-5zS!qCLQE zK}$j>w+J0?x2UE@-8>@nNY|qJ8g+7uP$#ztb#jYPC$|Va616ZAwGb7zFcP&e616ZA zwJ;L3Xg8*xx6Zeq@GWS23##6NO1GfTEhuvfn%siwwxG8yVne?b8%FPPX$jJxPHvH{ zlUsy3xkYGgY|&m#r|aYvp-yfIT9P`sMfk7$tK?6B?$Ir%N(*|@f|9hLAuVV~3+mB= zZnTI`KX0vQK_yzyhZdBfC4AcN58FVU+#>tS%7uH=gg>s9^P9@u_!Gi^&mPVPiBBFR zK6y~_##->8*gh}x&fy2ew(d&l*IiC+iXN$CJP!7i{H>;bbpIgecci$<}h_lrHF zcU(Ux_Kf7k4%i3T0sCOEhx8VH^%8a!^v>Z2#i~%O8a=ankT~i=jXTOkDR}>3y#H|WBT8w7|5o^K4Ib9>t$|xbYw#rKnQv?G6!w3{Zd2~9 zdcV^@L;ADWhrllwVWSl`T9wOs;9G&Zgr4U8{0ND*1WgANaSy zA8Pb@Ecmag#bd!Ak^WimkHOFJ*FVMf8row)Cw4d31NMT?@#L4lFN0qJpXaY%#qP%* z0EfUANFT<2(MY7>JB(D?q}O&n7PP6%AA^&RiD#uy>yOJvYr)6mBcYf#{)F&xeD^rM zdmP_APOTr;d)k7>@!jM2?(x9BdmP_Aj_)4FcaP({C-B`9`0fdO_XNIs0^dD>*Pg&f zPvDU!@W?)Vv5#-E`|!v<{r0h7pMGn667-n9Pj7I_0qkeQ!#;V$>3^tl?vqEH@<-rj zRnC3EA7g)xzkXgd-51y|`+|1TJ3+5!?hAUbd%-XAYG2YjqxSK|Z699Shu8MWYbsUJanY0b;z@k*B))hOUp$E~ zp2QbV;)^Ho#gq8rNqq4nzIYN}d_uon4?dya8r^#LRkCqEhdH{ny% z?kQ^b6i+_IlTT5*r>Nai)b1&2_Y}2zirPIz?Vh4`2dK*d>T-a(9H1@-sLKKBa)7!V zpe_ff%K_?gfVv!@E(fT~0qSyqx*VV`2dK-_J|Q!B+9zZNPb-&U;p5jkeuJ+inZo zw%Y=??Y6*eyDf0rZVTMD+XA=kw!m$>EpXdz3*5HbXxnYH?KawW8*RIdw%tbCZli4< zgpGsna8NvS1qa20(W-lp_Ha-<*tY5(r2QO3bq}Jt2T|RFsO~}Wa9+qSgJQsF)jdev5326At-1$QcmLI@dr-BuZPh)fS{tpp2UTm^R^5Zt z@gQ|Pi0VG0+6@QKsCGiyw=x5xs zp3(dD9=+fA5v6|yem(;;pMjar(6c^6&w3VapM~3JmHUa{S>oriemA^dd+e;vYKhbYe>{B?*@9imi+@Yf;ybqIeQ z!e58**CG6M2!9>IUx)D5A^dd+e;vYKhw#@S{B;O_9l~FS@Yf;ybqIeQ!e58**CG6M z2!9>IUx)D5A?kaG`X0hxe;9m0>)U@MMOqg+8vG;ajM35HXO(_J_~S-+{wzFyR?jF! z&zuN8#~VH`mQDnp7fV8X_j%YD2Ozp00QL2nc4SL?#hfmWd}pfF$18=UUieL?TB z{T0x)`-0x)loyODgWjVubOm38$uGj>7h&>?F!@E8e2!W_N3EYj@tz~E=V0JD82A#; ze2Hhi#4}&wnJ@9omw4tY%4I$HigFR^{l>QlzshgF%5T5QZ@BxL{H%YQ{5-amb|g6nz6c%xzwS3DCwapuo#G zkA&9hk+1>#UgsYEte*6Z09``=ABXP+kR4>59*pVv(ZK(|FdCe|cD+Ue*JU&~3+AOK zqrt3y8!Yixuau4k%e=??Z%2a_o_rl#<*$DOx^|<%MV|bh*j_;$4c_2am#{B`-lIDz zuQ``%;B``N@Xl}Z%-@242Yv^<$&=s3z6IXq`8(KuFCIpN@9~~>QvLz^A1TiUxJmk5 z@J-(KFW42MTD8hU3@}Oz;M)+yo?5l>Q+`|WKl0>%a=uBA@1w~!QV#N0&(KGc&ywGGbqsdcl z3CYv^>I^sy=6Qw|l03&7=D`B!b@O-~KPs|A{C68G8eJ6Z;3))`Zby znYwsAXEf>coY7?6ZwM2hN2}5BNBv~zF=#aW-~HRr>sq6s*Lg<6w}AKXq{qk6&{5N9 z*ywtNZ^icRh0*Z;;K`qLF5%B%r}*pJ^kkO4nWYbA>4RDIzBQ#=$FfPcv@9(rOFPNZ zLb9}tY|^bFn{<1~Chf;;(jLspFGg!lHu)`Z0kraDlV&NKG(Xv-naQf|Le3rN;uR-&kOk9z&(a)ZhJ#Yd3~U zj|KL~7%Dx6N{^w^V@a#@7%DxMv`UYm(ql=h^jOj=J(jdekD=0INvqkITCCsp4Qwmh zSkfvzmb6NbC9Tq9Ni#f_v`UY`)mV~p!q^xpJ*L*^XROj=YAr^q^jOj=J(jdekE!Jt ztuqp24u|7#I1Y#7a5xT!<8U|*hvRTK4u|7#I1Y#7a5&CLHx7s6 za5xT!<8U|*hvRTK4u|7#I1Y#7a5xT!<8U|*hvRTK4u|7#I1Y#7a5xT!<8U|*hvRTK z4u|7#I1Y!t*Wg6pdkuteI01(fa5w>n6L2^IhZAr(0f!TCI01(fa5w>n6L9GJ5%f+t zoPfg#IGljP2{@d9!wEQ?fWrwmoPfg#IGljP2{@d9!wEQ?fWrwmoPfg#IGljP2{@d9 z!wEQ?fWrwmoPfg#IGljP2{@d9!wEQ?fWrwmoPfg#IGljP2{@d9!z1X=5%lK>`f~*R zIU>f^f+O&A1Qj|W9+ZL>9YKqZphZW}q9bV05wz$CT66?0I-=V7S5~AWs-4lgbVRx| zEc6(0L^XAad)yJ!=?Lm{1a&&1dO6)1bp(w%f<_%dk&d89N6@GvVUuFuqr^W)iF}T# zc1j`gIZEVnl*s33V68Z+TKliwYj;$&J}>+$>}7fXXz-eHJgRuc_8Zt&!0VtD?5N@m z}$=y_D5r)`g(M>Trd_K0~@ zBc>RT2UQo1l}`UT(4*wh@NN35ZzO3GdiwX*kdQ$z`zxDVsseauh{7Z0#XD9=G zd6L#XNnf6X?MYhuB&~gtemzNRpG;E1b!p7(qt7kECBlRo%8j!|>c2iu;HIqh0ie`$)WQ(<|8@OKF53{hvw&)kK~e`kK~w-@sxR}AT=E3zo{!`hJ90_SM{uh2 z19NC#j`>I~>G?=5>G?=5>G?>G`AClWNRF{Am-Kuj$B33odOnh4T+1arAIT*h{pOgD zW`so$MIc}}1?C+V{%>6<6%n!} z;)Ij5!;|RDN#cZ)w55}@qLZ|rleC~?JBdb} zL^)5w?MYZY37;p46HcO^Cy5hI5+|Ib7AJpL3m7;_obWQ5_A;9GGMe@>n)Wi9_A;9G zGMe_XbbdW}8BKc`O`C#^DcG2TjVaief{iKIn1YQd*qDNiDcG2TjVaief{iKIn1YQd z*qDNiDcG2TjVaief{iKIn1YQd*qDNiDcG2TjVaief{iKIn1YQd*qDNiDcG2TjWe)u z1~$%Ugk2BLNT1h)6WE^Fosm`>Pk<*uM;d3O+fMgBy)(qyX97okXJF%uMr1#;0D2$J z8ELX{!wBx^P- zzlV~*;hE$j>C512{MC`?8TD?Xqq{Te-Nx^Nw|V9+=$YUdV(&9V-DhCG8BIprD9-}k3sX&N4;sr9r}PH&US`ALs`)2grW8tD2?QQXjq=nePXv-D0?8AB*22qaGgk|zSm6M^K3K=MQ&c_NTJ5lEg0 zBu@mAN3runAbE5UXxiBAQq4Q)$)vjGo`+i9qs1Ao(BmM&8T-6J%683@38Ip`;2;rf93i63~hdfHb2AseMYTa&(OkW)WY?oTC!9A#BWgR zwcUm7`TLAouhTt$pHb_z?fLtR+O5&^_ZjBzGfB_iXVe0n?)m$STA*#u-)GRq8MJW* zZJbfNQms+Q8RqXZw51v5?=xuT3@vAdmNP@knPL7uqqgBU{}XTa{C!4k!?}3=KBKl_ zyKcnSGtA#-@bwJy_Zj>ph&Y~T@aHh39ybTLbGF&p^50A?8zXBAgCeF1b_F-u%AOI$HaTro>rF-u%A zOI$IlxI(`oqL?M3m}RV-P5yV%zYaR0m{mk!{7cXg#jGL_qOXpD0IrMW5<(xzD=Fqx1;-xuS+8j~S z9PMom9h*bJ=7>D!XkBwC%pBS>hpNmG7tPUf<`|df7?4Iw-XB0mb!%gMqCISqZU~8EvTK?_NsnC?Zkieicmpg ztI>VGpwZLl`B#Be-vXPyPpYDe!y+6O;jjpY zMK~j4PVG$0Ca9D)HA{-Xsun31mI4r_p5e|!RScJnO92ViQ2!};DEW%+C4vTPD zgu@~n7U8f6hebGCK+_h`v;}dv9xR|~3u4l?TjK(nwt%KBplJ)zH2>9_wt%KB!1Dr{ zwm>gl5ZivnGo}TNWk%1K7ErbYlx+cJTR_lVW1qXM~DjjBf!O{iGrmr~DlFHl-|6_hst7 zOx>5M`)j1XM*3@{zef5BapnpU<_Zz!3K8ZC3b{grxk7BYLiD&ol(<5CxI$#OLQJ?q z9JoT%w?e$Pg0iikX)DR1*j`DNK#vY9L~$#`Z!1J@E5vLo=-3L;+6r;n3Q^e#QQ7Nw z@B(GNK$$O4<_nbh0%g8HnJ-Z03zYc+Wxha}FHq(Sl=%W>zCf8TQ05Di`2uCWK$$O4 z<_oCt19%5#u2BS1Lr}n48Nh$I^CDO23*H`J+SLxSR z>DO23*H`J+SJkfcTeT~r`}I||E2I1MRr>W+>7n23etngGeU*NFm41DdetngGeN~#K zC+XK$>DO1KY3GA$j3Cz-L9VG@Yr!?u$mm(mHFW+OI)6=NbBgDW*Yq~y+l1Hg>2-X1 z9iLvur`Pf6b$ogqpI*nO*YW9fe0m+9UdN}`@#%GZdL5r$$EVlv>2-X19iLvur`Pf6 zb$ogqpI*nO*YW9fe0m+9UdN}`@#%GZdV`*RgPwkao_<4KTMKT`({IqzZ_v|k(9>_w z({IqzZ_v|k(9>_w({IqzZ_v|k(9>_w({IqzZ_v|k(9>_w({IqzZ_v|k(9>_w({Iqz zZ_v|k(9>_w)4vVF--h9D!`rvv?K`A@hxG4|{vFb9D!nVXsdOP8xhanf%f2R$-^3#~ zRnrr)Z<79|)Aa^DuQwPEh>e>`uRh&m_30*SN;mP;O?gUhlc$Vl`0X_4o_JGUbNV^I zUpyGC(l_DZCLX*gHk@uHxvBLTW0^Pjzrt_IkH)&6!H+lb<4yTdZa@t zTh#OxHN8bmZ&A}*)btiLy`{EyKDb3qZ&A}*)btiLy+uuLQPW%0^cFR}MNMx}(_4zS z{RY?c7B#&^O>a@tTh#OxHN8bmZ&A}*)btiLy+uuLsm1y|uIVjmdW)LgqNcZ~=`Ct{ zi<)vvX230(!EI`KTQyymZMDBmO>e8FwypNJ>1Euf8E~Jb(BB+y)5~tt%WhMP+w`*A zs)c?fDL;Ji#neR~M zJCykjWxhk1?@;DDl=%)V`3^1l4rRVWneR~MJCykjWxhk1?@;DDl=%*2zC)SsQ06<7 z`3_~iLz(YT<~x-64rRVWneR~M?@{LOQReSalJ8NH@00$0(!Wpo_kWnKH>?HU*BjP? zbw-MHMv8StigiYcbw-MHMhcCG;(48sVmv3+HQl79o78lZnr>3lO=`MHO*g6OCNL1Xme?Y7L0j>HjW9417+qK{>W9417TiaeQ zy31I3m$C9LW941O%Daq}cNr`1GFIMYth~!ud6%*BuIi{asE$UD5qGKMUDeLE_fy?v zth~!ud6%*BuIi<9#>%^lm3J8{?=n`tNj&o=@ywgVGj9^lyh%LsCh^Rh#4~Ra&%8-I z^Ct1ko5V9^y`d{8^Ifd0_sC{-vCMjVS+nUjrTDv8Iq>RMnN`O!tBz&9ia^UY`Wxk7*1FvzFRa!skojK*e-#E%*-oLtp?VUMgdBG{( znNwDqG1EtQ>e}PFZc$PkLugneSrdz$>w3zKfLu@60L7n|_bK zi`BdSXpfAS7O`vDZh>HVr5oh z%k+^l-^I#&H7Ls?e)3trlkZ|>zKfOlE>>pEwyf6W_c$^s)3eISPHg|DK$*4NvRa+( zFOcs46euUZi2Y}vcZQeM8lCQ)Ic2_!mH94KriYgKE>=!@XHJ>#VrBJN)tB#LL#khqC&(ZSTw}tNk0jGpEdYa#?-B zZ}85XvU-GV&t=Md7b|Ne#OeMnR_41{neSp{^%|$sX85{M*4l`F>;Duet2Y@Pah3Tl zR+eta<9rt@^IfdWs&!dC&QJa`PkLugnHB7^dY^5t440)gF28r?l%+OC@60LlU98M^ zu`J5A=DXZt&c0^Z3k;;5GDXS-{UVIlTvmRbn@Ai}4nNwCDx9y!d zW%bsspu(72Va%;C=2jSUD~!1n#@vc}(0Wi|%&n-W*!JwKq84uS?5x6=TVc$tFy>Ym zb1RIw6~^2OV{U~px5AiPVa%;C=2q0A^ft!a3S(}CF}K2)TVc$tFy>Ymb1RIw6~^2O zV{U~px5AiPVa%;kyDIgna#B`RCs&*Zs+^ZnO}tY%Ruk_9e+B$ad51f)g%4u?HuwSl z`hSBT1|K4QFZM^UGuRJ!t|odw&rGX{KCmAg00+S#a2WKr z<|-$JRuf~`ef+7&&eN>8bBQfO5tljv8R6k6ph9d0~V%9H*rCxuoyDYP2;q|mBP z#8R0#DYP2?0=7>It#VRmHB6IoKPeHmPYSJaQfM`N7xoU)eNt$ZlR~RH5zF>2(%*yq zUTmKfS`B{%yBXXKJ^=n-;J*WZ1Ef!J77+Ka>$e}I7Qe$^e;51rus?)N{|f0}A^j_) ze}(ifP71AtkMQL0^Q(_yKZ@Oo{TTMgus@FdIQA3RKjiQer0fSj34RLnSNJL?h3d}t z#6yAZdQPK@TnXikjg#Ar^8=EP`D%<052niB`soH+2_^%%{GgP+2- z=EQ+DCq{GP;AgO{IWd|O2iBaJ(}`nFCyqItI1a2iabV4fbuORLniB`soEXiC(VRH2 z=EMQ*3eAbpoEXiC(VQ5~iP4-G&53mipGt-1#Ar^8=EP`DjON5>PK@Tnp*1H)b7C|n z4y`$HXw8X3Yfg;j#Ar?&T65yiniHcrF`5&jIdN#si9>5n99nZ?G$#(NIdN#si9>5n ztW)@m)|^Nayh33R)PK@TnXikjg#Ar^; z>BMnp&51*6PRw~_acIqnLu*bPT65yiniF$6am?w&u}TOQqK1;x(3~2YQ$urV^5BV}j^@S#_K&8ed~bu_1r=G4)gI+{~QbLwbL9nGnuIdwFrj^@S#_K&8ZXT z)X|(eaZVl0siQe{G^dW{)X|(eno~z}>S#_K&8ed~bu_1r=G4)gI+{~QbLwbL9nGnu zIdwFrj^@S#_K&8ed~bu_1r z=G4)gI+{~QbLwbL9nGnuIdwFrj^@+ zi4a1_<8d_a^L+Zxv%YK3ne#p8+0Xv&@7`yhvxzzL#GH9z&O9+^o;+usm@`kznJ4DV z6LaQ?IrGGvd1B5yF=w8bGf&K!C+5r(bLNRT^TeEaV$M7-HW$P=%LVbl;xSu2c8T=CXW$Q}8b73wR*!cmUV z8Z+5?r&Xx05DPV@A^a{^`#tP)*!l{w%Fko#{Uh0W7Ae%qe4*Yy5^D9hP_rCD&2k7e zCnnU2eW6zD3pFz-)U$8luRzUG%DxEdjY8R%z{{YX!UQoz9;3e2BGgxig__kDYDI@o zbNfQA=nyW!F2P=keG9g}Labl)6=I>jLM+r*h=uwJu~1(j7S@7wU_JOrP`$r?T@5M+ zkgcx}3(=cmk^O2cFGO#OMLM(etYf5P#Ih-H5WTVElTt*;OZZ>L0GA(s6S>?&-1g;*u}3bF8`*!l{w?2lpBVt*XF z4*L_>_1Je}-vzD!SAwg+HQ-v1d-wN3b>{VyG+=MQZp8iz>?Z7Hkank7X{u%h^ z;Cj_#0r9+mcwV3!(#JTS7bu6c?RZ|G9Mb4`UZ5P(z8(elfSQq3NjIn&Y1vQW_p6*? z0Pjb@qo6r2P?R=41L`{-vQL0t1HTSF3w{IC`%pS&1l0T2vR?pS1RbRdlph)$r3;AC z1&Y$Xo>9EKiv1e4W}a34I`;QC!yDlDLCrbq*M9|n4C-lxO2)to;5hh8@Za$`0ZxLK zK}X#JqHY0Ew}7Zypm?jZDbgBCz*|5^*8-wzfugHzeOFVc-H?UaIaKIKTR@~OAkr2n z$8(7zZGpe{F1(8)SGX4R1$v|QK^CF}MOrbh7;EonAg({i=80gw%4} zePw~>w_Q$MXnx!Fzi|xAYku3u{|5Xm_&a=QCST?6z`(oUyixt3c%TOJ{`N7Rsr z8WK^%m?LT!b3_e^s38$GB%+2fx28~|@=>8PZ1UYDh#4iKrnF zHB@eFzmBLO5j9k9Y}*kv)QH<&98p7!xQ&jep+?+BN7Rsr8fwJtBTs;isG;&+qa$jl z5x3E8EhM6b%6n})qJ|oA8y!(YB5FuP4T-2B5j7;DhA~IfPpTQ9| z)cD%yRvZ#hL*=)&9Z^FfYN-6yw%c{6{MP7*8fr9cbVLm`f;Kv$hD6kmh#C@6Ln3NO zL=B0kp+?F&PuvkTB%+2y)R2f85>Z1UYDh#4iKrnFH6)^jMAVRo8WK@M?JT5PAfkpu z)R2f85>Z1UYDh#4iKrnFHHZ1v zG4zp+s38$Gj60%+dM0jPj;J9KHPo!CT7l-VghbSkh#C@6Ln3NOL=B0kArUnU98tr- z5j6}PQ9~kX7&xMaMAVRo8WK@MJzI1+DkY+ZMAVRo8WK@MB5FuP4T-2B5j7;DhD6km zh#C@6Ln3NOL=B0kArUnsqJ~7&kcb)*QA0hW)HUcCrO^>JB%+2IU+6A~s38$GB%+3z z>u|XvYN)vm+m5KA#uqMeL=82*u6UFP~!{Rj;NvLI&3?lhD6j* za~-xFQA5pj_^KRHL(O#<9Z^FfYN)vm+m5KAMixd#)KD`UM&c3?H6)^jMAVRo8WK@M zjShUIBWg%Q4T-2B5j7;DhD6kmh#C@6Ln3NOL=6*;sG;|M4GLo(XBEagsw#|mtW>Dk z2BUU@5NeiK_($?p81uap#(Xb@niKFb{|tT!)Jg`GJPsZN`@nwAa0omMeg%Az^L&?M z&VlDatuD}c-UNRIUIZ^0H7+)41*UKbxD<51P^hflc!$p)*W9@9W1ybF$@W}IVcc^m zh1v~4cM*3T6?!iTp=VSI47Q%1kHwhh?3pKLvuM?c-Kkw@u?03O=V-N$~w-#zNt>c5w*`wC3*nY@o z3%am(b4&`{LrFK-1NMRkz~lC=QH;;)=l~#yv=()^hl4)8GvFdw%8a^v;Wp@#+hqv+hE#!4P^jqfjF$U5`dm zw(kZTz(%kMYzAAvR`AoH=N}3+zi0d`2zv?GOZ*RWnE1cI{~P?D;Qs>u7HsEw9sqZO zU(oeL>R z$UP!*kBHnOBKL^MJtA_Ch}>R$UP#>x%nEL zdqm_O5xGZ1?h%oDMC2Y3xkp6q5s`aD>R$UP!*kBHnOBKL^MJtA_Ch}>R$UP!*kBHnOBKL^MJtA_Ch}>R$UP!*kBHnOBKL^MJtA_Ch}>R$UP!* zkBHnOBKL^MJtA_Ch}>R$UP!*kBHnOBKL^M zJtA_Ch}>R$UP!*kBHnOBKL^MJtA_Ch}>R$UP!*kBHnOBKL^MJtA_Ch}>R$UP!*kBHnOBKL^MJtA_Ch}=ml6xe{J(A=eNpg=Qxkr-RBT4R&B=<;?dnCy{lH?vqJqhg#l6n$qbncPV zlTf2`k7UfbM>6KzBN=n=1Lq#e zz_~{h#=N?JTpy~?AJ(A=eNv&3OxpR*sxkr-RBT4R&B=<;aX4S_y_eg5B zs=YY(NRoRb1Lq#ez_~{k6J(A=eNv$3A8Jv401MZI8BT4R&B=<;?dnCy{ zlH?vqa*rgrM^aBAb%um65uBbjjSk<<*o(YZ%5;oKuh?vW(-NNNt>CC)vP z65uBbjjSkxV%ENG6(wP1g|2v)Y z9`#3k!UvVL-sAl>gb#ragU&hcp>MiJeUp#Tekj6jQ2U|C)_y2L*ZV!{n~YxozX<-n z{Q7^ezl5#*P;|^e@CnNMu=|a=FI}hZ%cymOL7UzhctEJtyh1-MZBq;}YCk)n^ZYhN z5~D{kZHgg7!j+)MFKs;2Y*P&JF@C1m=Kbu1ex})`7-IA@%{F318&3?|6f0cfr-W@h zA8g}!UmH*Q+IYs-rdXk06)TK>a@VFelRk}p4%NmJs5a@-wx2q+NuNeP zX=;-`jaFS7s%t}aZK&=uemf)hjCdBxyHW3(2OFiXA)%iuZIrskgg(zkWi>{v(h*wQ z8{?nHen9oO5%q42cTnC*xs|*z-eXiP>ujoJqqV&ycDJ5SZHavv{7mdV*&D&X1~-AP z7~>T_CSD1Cijo}odt-2?%Y#pXp9MD?6QB35iGRZWqVQgMyifRP&>ru_xud*#vg7lm74cq&Je^)^mf<>TK45~zH1%+C-Bh<=3 z;rl7MRW^Hm39aY9RX#i>{3&?d2q)~`C3`0&KQ!uW##@EkRgXi$$f){KiE7H|Cz#vS z{~BMVyRKR2cY=hTmuQdwQfF@WyF5a@r7Luv(C)X) zf_7cAj@LCC-FLM6T^ONfN85wPz^6dZGqlsQw+Aoq-xtBN{OX>)-EWx*wL(?+9m>5n zquuYD2=$h(@Cx>Cz_&SrXPeuDcR@$U_C$eyO%#HjRcNPAZ>Jysyx*S+KCkS^Xf=Lb zd699#sI11=1?~a6!5**|JODlqo(8`Oej9uZd>yoMKCcYKI1SE#^G1y_jEg|`5uev} z8gCKqP>nnwbicMkD(Vv+13izlgKOR)ExG(v&|2ETUF=XD$yV*?NY##UJ0%bKNablm zLig7@0{3w{f_`ui^lF?P%FSHvd~63w+d)3IgM4g`*>t z+iPrgNP$MPvBTff7EXe{r`&ApNF=}_d-NNB!uMdClO1ZOIs;nW2{SukW~augW5G_1 zQjO!Fb-NSY?$n6Y<$gl36W#9A=+z~^1+C$ou(T7Fc4{=LUs3W-#-Tek4t0rDy%SaM z)R@%vK5##1o$u7h)M%aW)cDl)H$cy%?Ud$S@*-%p?-XP9{%z1|-|6=ag+Ha_SJ>A; zN1mM;nHsJ6oq;vK6V2~b9xGd8RsE__s&TQ}<4$SG_+Cmpd$vOfZ==&A!O@za=&F-g+KH|@ z(N(8->(jrit4?&)NfhlwSDompQ?nmFvvt*}84sg%)rqb;(N!n9>O@za=&BQ4b)u_I zbk&KjI*F*A=<4sp@YUe&#O>AKAF%%c`$4jo2kB2Ar1yM~jN(D50_!NHbTqon1UcAN>%0^h277(!badsxDE z!FeP7-NW>E537z`qW7?b%Y|L=-v$3&YVRr`d+buHxBZOH+(q`-C01>p1f4y0iD8#G zd+Z{6?DAK+WPb~E_SogGatWP1cBvMO&K|p{>n`fLi|nzB?6HgNu}cv{=RpHqXrL=_ zzuXl#d+buIFuH!b&_);9=puXUQk3woTnX7@7ujPM*<%;kV;9+DmulJPbidH0IY6Vc z$1bwRuE5!2SK#ci%U|UZ=0InUU1X14WRG2FvI|Xikv(>iJ$8{jc9A`HNg?_I0t(rM zLUy5$T_|K13fV;-wF`ypLLs{pEA%fEvI~XmLLs|Q$SxGJE3iU#p^#lDWS3gE|7C^j zLLs|Q$SxGJ3x(`LA-mLybtDSeg+g|rkX+U`kJe3x2)aLfNl54yNR5;iI%%{*Di7YyW3xL6}lJRtvO_$ zOT9ln_qEiONI6FL$h(!5=@_X<*CQ3_NM&d)*~PDWu-z~3){K+O-8=8rypwIm z(%nHX_5sj6^=^O7Rj9AI3a$3tMAzM#iL&j!dpEt$Zu*_w>UUhCuel0e$Nnz1=LvU9 zb-pTRIJ=pJ-YwPnYJUkj>h4w^=rcR^?p8Kv+wp3*W~FR94)3PV+Rgm)Zes6l=AU;H zfp<%L@+Ixbm$F3ti#WWS2)tW5^sl$-*GFjekI?ELAwoVvgnWbu`3P#KuR6 zijNTS9wFAHP(=z=q)OANqona_Or=t0ILeQm7&&ze6gqDpIH-g(_00 zB84has3L_bQm7(@DpIH-g(_00B84has3L_bQm7(@DpIH-g(_00A{AH_DSFiusz{-V z6sky}iWI6yX-?8dS`{f&kwO(IRFOgzDO8a{6)9AaLKP`gkwO(IRFP6|JF4?o6)9Aa zLKP`gkwO(IRFR@DPN9kvsz{-V6sky}iWI6yp^6l$NTG@psz{-V6sky}iWI6yp^6l$ zNTG@ps(2JtJc=qFMHP>tibqk!qp0FhRPiXPcobDUiYgvO6?-_t9?r0bGwk6EdpN@$ z&aj6w?BNW1IKv*!u!l2v_t>GpyT=MS!(Ps?mow!wHRrbp|hN9(3X>!wHR4&0-4)1!6Mqjl4x zbbnP)H97=|LeqD5M94^q`O)6w-r2dQeCY3h6;1Jt(9H zh4i419u(4pLV8e04+`l)Aw4Lh2Zi*YkRBA$gF<>xNDm6>K_NXTqz8rcppYIE(t|>J zP)H97=|LeqD5M94^q`O)6w-r2dQeCY3h5!|=|LeqD5M94^q`O)6w-r2dQeCY3h6;1 zJt(9Hh4i419u(4pLV8e04+`l)Aw4K$KML88LiVGO{U~HV3fYfB_M?#fC}ckh*^ff@ zqmcb5WIqbok3#mNko_oRKML88LiVGO{U~HV3fYfB_M?#fC}ckh*^ff@qmcb5WIqbo zk3!f3I$#gzpcjSoqL5w`(u+cRQAjTe=|v&ED5MvK^rDbn6w-@AdQnI(3h6~5y(pv? zh4i8jcA5^@X;$$p%jF|rzt>!UW&11y;$B6ll5%V7-<{x0z?f|oP z2jXY+-vi8493X!eG#$wL3sRet>@b0R8v@ zdhi2Mmw%lCJ!^MBeX-H=2?v<9JHV{n0qM#me*oSh{F1+j9(>7PL=V2iuV3QVFVjZ8 zOdI(!%KtLT_fFhHfp_8-N{3^@W1#1XAD5zxuTt_0&@+3F>kKN_8H}DQeq5Rv6?(4t zap}azc&6oXsl~r~uK00j#=m;5_;IPlB`<@XD}G#>F?z1}an-d@_1h=(T=Cv&zW@o#;ez_V%xnf*A( zT*pD;z(HNP{-tX+dan4O<~NKU2OZQkyWDfd2UT}Q&z>Dr-evTd=%8xQdWXEvnRX<( zPtXRRpbb93ti=<|T0EgPsQ;?f=t#9b+qcT@LwkK_uaElbBR=;LpZln%2azv^izEG{qd)M|*nagy;!v^I$8=Ki_x$>g z*nZaEPjv0q*ve;cjP2Lh%C;kIzs6R!9dG+JwzBQ0+fUT(*VxJ>ezM=M$e~)$SI%sI z1@xR)fACe%vwZ#f%9-&Qjyw*2of6Oc^#>>|K7)IxevJ)XzRb*MglPNypku`$ zwYyQF=RXfAni)NQKctvu+p{`{;+{`CB%K=_T@Nv4KO}wX81?4HB)HYTN>?uNT+JcI z0uA6hxLCm;7gRdhdr#U%xCx}w)@$`WF3d;Z4XOPKE{3SVRDSa zWLAe&n>sW3)nV1C?PZ{Q;jeIyU*R6V!qt9-tNjWZ_zD_e=lbADauW8hm+joi``3>O zeb?T_en{xMW-oi;|Bn5M@L|Pp_O%zfy|b^q&~2Q3?S;;%o`eT>x0mgf_#|vRsouf1 zdus1=Z+y*u>93zuyR+?H`$;k2U)?&`^4E$44rt!B%#XaLLP+u~ay${s);$;65cnbUm=y~|3=+U0iwb}j^cn$Pyz*ADA z(etZMNt4DT=$!B=_njH9PGTx`jc%LTQf0}InX|npK$?Bg*15Z=? zU-gav!2o^V0JS+lA2&d44p5r|)aC%SIY4a=kQEP5n*-G505N=k+8iKm4^W!}#OeWR zbAZ|$AWt5kHV3H90cvxA+8m%Z2dK>fYIA_v9H2G_sLcUtbATu}Ky40Cn*-G50Q?M4 zn*-G55o+@YwRwcvJfaA5H8?_Ma)jDELTw(QHjhx7M-)$V47GVg@x-=k^N8Y!(Y1Mm z{NxC=d4$?LLTw&VZ1JzI%_G$25o+@YwRwcvJi^r;;cAain@6b4qtwMwYT+ogaFp@L zQO1Bri6lqi|0rX+qcDFI=8wYsQJ6mp^G9L+D4ZXK^P`O8juJ7BGMYQ8>mLh_it|39 zpQ;~aGde~dVPj5vRcD?diGKSs1aMw~xJoF9b$LHHkp|3Ua4g#SUVc@X{w;eQbR2jPDZ z{s-ZI5dH_@e-Qo$;eQbR2f6Y=_#fo@2jPDZ{s-ZIkh>U!|3Ua4g#SVKALK3u;eQbR z2jPDZ{s-ZI5dPWAK42Gnp?lb8;Qtx!g^yY9e#DxeTFM%r+C@dDDm@%XW;)CuACj=1NMR!dMyDvzX!+R|2X^~hyUa7 ze;odgbIr%$|2X^~hyUa7e;odg!~b#kKMw!L;r}@NABX?rT={YMKMw!L;r}@NABX?r z+{JPDKMw!L;r}@NALlNP!~b#kKMw!L;r}@NABX=F=>G)zKLP(I;Qs{oasvIIfd3Qd z{{;M>fd3Qle**oVfd3Qle**s5Yd&C?d7=3~f&STFUbgd`6Yzfm{hxq;c9{=Op#Kx- z{{;M>K>uH(7x)_U3}54pzQ!GWow4568S8zW5!}}q!Fh-GzQ8-Yj|n}VdY17N`@GBc zcVhj*jL zQ_sddp7P%A{;$VV-r?OP9#46PciSFMd53q~9#46nciSFMJsbCU$~(O4Y>cP8!@F&d zr@X_v(c>xa@a`i$p7P%AwmqKm-tIoe<05F^a(9WxQ_nJ<@_z2NJ)UAeccI5q-p}17&U>F_JoRkg@sxLUcgYaw z@f5qd3q77w2DJob_DXD;1y9IP1@a z-%>q3r*W1`ej#R_BceVRzhql6O8-@iGCt%Zl@C84^cQTN(>P22Qby_{hkZ8XqPEkN zXTX=hmnnaRGkBc!oO%tP%~{iP(&wnqvG+OY)3$rS=hTDv7-vw=slTx8eCavm-twi| zmoH`Dw*LUzs}P@4zB&|~)V)6-^!m(`y01Q=S2eaY5+sa*GE{9Bg?p6xuzZ0AX4 zJ5TDqe5B_&PX>;$Ct1gJGVl!NN&3E%T>nX~{3O?WlB+$*b)M7}>ioJkqh~CiS1pVQ zkAWWNKF{p&^Ncc{XO!_g^TW@JPyNb><9S9L&od|dJmZe%8FxI-tnVrMlvDI6r zrTf)>9?Tz}ihHK(lsRJOPSJi(i3gW>)zc~Qa5XqhesY?g>oh&rY4VfP z)X`~T^=TsTX>yX&wqCMP*fPI8(WIZch6CgPnY)}5wCPLqM0CIdN5 z26CD@I!*3zn%v_w@#Hk|Vzn;|`rWYPoFYIG}40=WGu;P!8{2BHbsQn#O;+f}R z^}M?Gi`MVItnJ`e@tKzUDk_d(LxMG2CbKOv|t$xoyvR z4ig)P6-ms=i=bQCuv(c*JPSH3^%@4)ln1SpANV^jzq$dLP@K z`59IpWZQG0!-@dDe!mGdtO#IqZ$9k(9fSc`;3J6!!)kH*ulg^eBk-_#G2>#6^z)rz z_2$MUzAx|ZAoR@Au=+S3=?Fipp3b&cRSm1Z+Z&9~n~%_&kIsy%}40XN9fH*=*>sSDo5zeN9fH*=*>sy%}40XN9fH*=*>sy%}40X zN9fH*=*>sy%}40XN9fH*=*>sy%}40XN9fH*=*>sy%}40XN9fJd#G*7&C`}YfGcHIo zB1khLNGl2r1!>}HnkbZJERc?$qugVGG-H9ZnCX-4d@rrY<8tSF>A3U%w77D)^ZzvY ze_Fh`#QA@k{68%YUGgUA%s;I-WAD!V(`5c>MHt)8{L|$8X~mW?*}vgm&ivD4{%P^= zv;B@UIP*`F`KQVJ(`5c>GXFH0e_A!NH%Jpt(y9?1PoAG9o}|h1)8zSS^87S;ewsW# zO`e}tp06vRmr1K0eFo2?rd6A^9X-;jQ`?RnY1OQ4M~^hUPg=F?66g78^87S;ewsW# zO>Uniw@;JXr^)KmiWolLS$&#_k(N$;9!HF{)MDGSvS~#FqjUJQ;(+b%g3j2}WbA1& z_B0uLnv6ZIw(lc7YD<%`r}f6YOPsf-1Lt^YviEf0v0GZ2v`1$MX}x#v674P_bRUr> zpHGv|r|Cn}Qm>D9Oh`+^wjC4FQnKxN|BFmMO(vgKdv{6XU)A1iC$XKqr|E6eWbbLR z_cYmin)aSnEAYQO1C~}>uP6bri?pd1X;UxKre35?ouM6_VFv6Bt>z4^ z<_xXoj55`+;0&rhqbyW5>N>-7kTX07IfH)Apqw*k<_u~%gHFz%lQYV}{9k8WXQ<^f z%CBs{3_5!h%ZvnHW+eCu$G^hyuW;jBvG|Fy(#|XB4iqYbzx;U7~a93_7RL&vUhkA3Ftl#ndP@t}NAOrmoe$sYg)s z1-)Wwl-eKF?6l8dK1VgrVB4&YYCgfXxgBNPI?A|plyU2*W*GddxgFIgTQ&@gYOHPB zvrMCmxJOaKtGf5T;8oqbkh^9mzn}G*co6E!jqeeDhg$m%we}rq z?K{-k>zwCx&ht9wd7bmT&Us$vJg;+}*E!F3InQ@F&v!Y`cR9~FTKYNK_&M76IkoYz z;2cjG&Z(AV)4I>`l;Irh`W)^09PRoX?fM+;`W)^094-1BE&3c!8P2I5eV%WDo-aDb z$mkqnqH{cDIHx*wxyLN$Xv^nl%jZ<5E}1tn(m%&Y{~R@YjygR@>pn+3d_%2!EOV(}eJ^jQbsPPZ=MA-Pqo4A;q1J8O2l{=MH`Ka~e#-WSTDQ@6@rHD0bS!#< zDD(zb{)Sq*ORj-_%JYU=y3tquhFZEY3Hm9|8*1HS!FgKWd0O9jwcPuH^VIBlYT-Ps z@4S>bs&Zfdc_~viZS6d5?Yzd_S5@K`cV2qXF~K90q_7{wehfUpf33Ik(y-6t)fDHY zWS_xnD$YyKw*BAoTnw8=ibjV-*sN~ zrE@atbzZe*d%5uY+}HQ<@_o+#ea`TG{`&)>*$;?jKcL2cK#l(pJ^v6r{}4U@5Iz5h zL2uV~+nZ$N!Auf5!1YX|*|u&k(C#kK?k=dlT<&&v zfp>c^@NVw~)t$?&+Y7wgdqFiQkMMIrHE7$h^nzlkjwG`E9L9bQV?T$npTpQVjE%$C zIE;Rq!6Tvy~2cXA*6Z9Gr%H;j4G5LN1|@ODN1hNpb0F za0!K6LLrw>$R!kV358rH54=nsc$qx#GLiW*wS1Xqe3?A(GPQP@Jn(X`M{Hjv54=ns zc$qx#GI`)-^1#dFftRVN%S6}9)YoOA>t&+rWuoh4>g%%Z#piS$c$qx#GI`)-YUDC8 z_A+_kW%9tw#MsM3*URLAm&pUKkOy8N54=JicqRU(&UuAeN zOTR`-zlNV*!_O36rtmUF9GD^wOc4jBhyzo^fhpp^6mejRI50&Vm?92L5eKG-15+qu zia0Pu9GD^wOc4jBhyzpH(G+(yMI4wS4ondTrcl%rcRIzLP7w#Dhyzo^fhpp^6bwwk zz!Y&{ia0QZx~9<86mejRI50&Vm_k=m#DOW|z!Y&{ia0Pu9GD^wOc4jBhyzo^fhkls zMI4wS4ot!P6wFT%2d0PvQ^bKO;=mMf;2Je@jT*T|9JodtxJDefMjW_C9JodtxJDef zM%`Vb?yeCBt`P^W5eKdj2d)tZt`P^W5eKdj2d)tZt`P^W5eKdj2d)tZt`P^W5eKdj z2d)tZt`P^W5eKGG$TSL>Mj_KEWEzD`qmXG7GL1r}QOGn3nMNVgC}bLiOrwx#6f%uM zrcuZ=3YkVB(Mj_KE zWEzD`qmXG7GL1r}QOGn3nMNVgC}bLiOrwx#6f%uMrcuZ=3YkVB(Cls3YkG6Gbm&Rh0LIk85A;u zLS|6N3<{Y+Au}js28GO^kQo#*gFCls3YkG6Gbm&Rh0LIk85A;uLS|6N3<{Y+Au}js28GO^kQ*rE z1`4@>LT;dt8z|%k3b}zoZlI7GDC7nTxq(7%ppY9VLT;dt z8z|%k3b}zoZlI7GDC7nTxq(7%ppY9VGK)fHQOGO`nMEP9 zC}b9e%%YH46f%oKW>Ls23YkSAvnXU1h0LOmSrjshLS|9OEDD)LA+soC7KO~BkXaNm zi$Z2m$Sew(MIo~&WEO?YqL5h>GK)fHQOGO`nMEP9C}b9e%%YH46f%oKW>Ls23YkSA zvnXU1h0LOmSrjshLS|9OEDD)LA+soC7KO~BkXaOR6NTJFAvaOTO%!qyh1^6TH&Mt< z6mk=V+(aQaQOHdcaubEzL?Jg($W0V-6NTJFAvaOTO%!qyh1^6TH&Mt<6mk=V+(aQa zQOHdcaubEzL?Lrh$lhR13K1&vyejnCj5(>|0pSq#FzC6dxwyZ4GN+7mRQNq?uXUUw z$D5M|Y+jE9<%6?t$nZr5dz_y(?&M6bN?Ju9qkwebuS(ttKdpL8- zja}~9#W`ijwmru@7Z1kGXIc3PUz2-74NIjPR* z*~mF%(LS^1B=PKu_3PWT`nPHIZ>w#N1#hcfjQ038ZS!r~=G)@aC7!2!TW2=jD$JsaEUL(& ziY%(gqKYi4$SOYc1zC+#t_rP+EUL(&imdz&sl;c_qKYi4$fAlYs>q^>EUL)zJ)A76 z$fAlYs>q^>EUL(&iY%(gqKYi4$fAlYs>q^>EUL&dg2q^>EUL(&iY%(gqKYi4$fAlYs>q^>EUL(&iY%(gqKYi4$fAlYs>q^>EUL(&iY%(g zqKYi4$fAlYs>q^>EUL(&iY%(gqKYi4$fAlYs>q^>EUL(&iY%&l2UWa-D&9dA@1Tlz zP{li_;vH1+4yt$uRlI{L-a!>PRFOj!IaHBD6**LqLlrqxkwXocTvT=sKWa`?+x;->h$i7rk(V~cbfn){&r2ag!t;tJc~*7ibtOKJS9Rv;N%Qf4<#?~^%*VYd zFCX`ud0toRBfY9KANLBqycFesc~xg#>p+Z-#(DL{_Tp8Yc`3~$o*~S~y<#UX)fv62 zGta8dy!vP#=~bQixL0-N<8N`iS9Rv&Ue%eGN_`%$^vg@7w!Nw|FZPUH)tT4I5TjRh z=CwY=wpVrLS=E_mRcD@6o!%*1|3c4s=~-uERcD^ro;<5M^Q`L3qwu_H#K(7Xw!a6x zsxzCm}t(5QcJc9iw=$WFtT7k>EvAwD@&#X~it-y9KExNS%B!vU3|`flS6j2~+1fm-I`g! zys9&=RVc=vf@p@8n5QM?X^DBYM4!hqy?Iu3=2_L5XH{pORh@Ze#`0>XK608Py{a?M zELmRd)V7~o=arlJUc9O^uiVV&Rh@aQLNR((XP!JQPo9>iUFX%Vbq3mXUhUfUVvh8x z&OB{Aua>C$VpV6JIk&u8x{vg#&b->YZRZAgwbs1}@4en9)H5TYGKxe@ZEaMjnS9|- zvHuNQ?`WvxO;9T_WNRgcQ156YVqVoL)H@o&1)$#1kge5d!mU11`t*@{6IrM=8$zww z5NgeaP-`}XTC*Y4nhl}e(GY5#hEVTl2(N>BMk z1b3Ipx{{{ijf|NT@fzgumgxTK_3qZwv|b=9lny*jiO6dj?x?e#xH2 z)|+3l_2yS1L2V|e%>=cXP%P5FsLh0GQ)lz)HQ_e<_3Aa@4s)nl_K}^G=*=(Ldh<)D zH@}36LPEXyB~;`Q>dh~qB9BmSehIZARH!$u#T=pD{1R$Ks8CWt2l7>*x zMyO~b)T&XTMgc;N0)!d`2sH{2D%uFqhN6wnrj@`#z4;|X4~{mn(SV|jdD99~q2Bxw z>dh~qqK)uBK5-QFJHDVHKrKnJEehC$4go-K&H3R?4YukhcE_YNB z>Ps5J$j2+H*w%VZ;bMMuOh_oIxMT^x>di0Nw^06G%Jn4;mHZ*L){e^7n_r0qYImc; zVk7Znfpa|BjwcJ8;|Vn$6ly#u)JRaMwI4#qlLgY7F$rqiC)?3ufipLu#&^PfpvH8v zwI)QUQJYZXHKC)&0%uS{jkAOr?+7)vN-S`WB-BVs=;*P)8Ie$HKZK4R3yBg7y$(UP zqrpO=!9wD|Lgf!G(W-3WCEK)xgI=pa-naUy(6PNhbzyXDFVGzs9pMX9BSNBg0dc#4xa~crUGBJDAm)vZ+Xck!0<{#| zdQK|T6H=jLxOa{=I);1aXrrEgCA@RA(UH7>t1aMa3y9|h#Pg842&sjTS_oAOV~NnS zPeRQnBtrNPJ)0!kaVCWM5avUe4`Dup`4G-SI1fF8q~kTl7jjNrzwOtg=g_%?@F$dL z>@3@Chp-*OcIcTRmCPH}vW$yBx2BLg3gJKWQ?NuK{1?K1A^aD@zjwp*ujaoH{tMy1 z5dI6{zYzWl;lB|63*o;I{tMy15dI6{zYzWl;lB|63*o;I{tMy15dOUr3r1?YJ?n4)8{?Pb)(5hIZSwxi(ffs2m(YE98BF`ua&HN(ID+kiJ9n-JylY|ZLEann3;N8TG zzH9FwW^^BvRK9A|`bputpzkQjy(GB{_7PKwa#5kLxrl2n;+l)lOc9zXLNi5ZrU=ax zp_w8yQ-o%U)UI^~%_uvluoO!^2`2Sj@dI=B^iW zzl*uA#b{yO9v10fshM!`XDTbM1 zm??&nVmK*AW5sB!7>yO9v0^k4 zOJHdU{49ZuCGfBW29|K|OStPL-0u?ZYY7@#g2tAhu_fHm67FRQcd-PGEkR>TxaJbB zxrA#jK{F+2rUcEDpqUahQ-Wqn&`b%MDM2$OXr_ewE#ZDkxYH8uw1hh?;T}u4#}YJC zf@Vt4ObMDPK{F+2rUV{J&`b$zl%SasI4MChC1|Du&6L1W37RQ^s}eL*0%Ij;rUc$f z&`b&Jm7tjtI4nUkC1|Du&6J>-61XivGbL!I1kIG7nGzT-K{F-rT!LmwV7mm(l%Sas zG*g0RO3+LR{4a(7rSQKL4wu5=QZ%y^CYQqGQkYzdX0%VcV#QMUTnbl9VQDG+EQO7w z@URpHmU8b)x$C9e?^5n-DVkY|W|pFvrQFd{?qw-=u@ucLMKepe=36wP8A{xu5sgp^ zxhg!VUgj1l#OUnt7SE0fJ%+kPHLhb+%f=M=B*zScdZ$3;dZ$3>8HQU_N5%`3I2*r3 zHDbK%ahnyMIEE{ zii!7%0b?QPY~;Pv-7RpCzmr1cYvub}Vu^h!NN3qLM>~a*l9K|k2 zvCC2HaumB9#V$v&%Terd6uTV7E=RG;QS5RQyBx(XN3qLM>~a*l9K|k2vCC2HaumB9 z#V$v&%Terd6uTV7E=RHN<9go5ncv5m-^V%M$A8~XKl^_A+4s|0-%nlNsyn@!xK(#5 z)b$(P&)%l;2ZWk|6y7dQZj0RkYNt`zpTNEo)J~%+(N3enmEbDPY24g;Sz8us9lLwayxjJ zug5!$3Ri-E#xa_SRf%R|g&sBE=AA}`dLuxnr>?@k@sZwXRM-GEf=ysE*aEhKp9Vhz z{x$en@ITMf?(-)e05xwf`wQ~&0r>v_{C@!cKLG!w@Lvl5rSM-0|E1pPHBk!xrQYdP zw)roG|5ErbjhX*a_%DV3(wO-#^-ixs^Ir=8rQYdPw)roG|5Erbh5yo+`7e!`|I(QG zFO8Z1(wO-#h5u6cFNOb7@ARs2^Ir=8rQYdPw)roG|I)bmFO8f3Qur^0|5Erbh5u6c zFNOb7_%DV3(uDah^-ixs^Iw`U|D_4@Uz#xgr3v$2>YZMN=D##y{!0_)zZCvUz0<2~ z^Z!Bk{~-K-5dJ?1|7GxB2LEO7Uk3kW@LvZ1W$<4H|7GxB2LEO7Uk3kW@LvZ1W$<4H z|7GxB2LEO7Uk3kW@LvZ1W$<4H|7GxB2LEO7Uk3kW@LvZ1W$<4H|7GxB2LEO7Uk3kW z@LvZ1W$<4H|7GxB2LEO7Uk3kW@LvZ1W$<4H|7GxB2LEO7Uk3kW@LvZ1W$<4H|7GxB z2LEO7Uk3jlg8vV}|A*lJL-1b?|K;#s4*%uwUk?A}@Lvx9Uj_eF@LvW0 zRq$U0|5fl`1^-p>Uj_eF@LvW0Rq$U0|5fl`1^-p>Uj_eF@LvW0Rq$U0|5fl`1^-p> zUj_eF@LvW0Rq$U0|5fl`1^-p>Uj_eF@LvW0Rq$U0|5fl`1^-p>Uj_eF@LvW0Rq$U0 z|5fl`1^-p>Uj_eF@LvW0Rq$U0|5fl`1^-p>e+T^E0snWv{~hpO4gb~fUk(4&@Lvu8 z)$m^p|JCqc4gb~fUk(4&@Lvu8)$m^p|JCqc4gb~fUk(4&@Lvu8)$m^p|JCqc4gb~f zUk(4&@Lvu8)$m^p|JCqc4gb~fUk(4&@Lvu8)$m^p|JCqc4gb~fUk(4&@Lvu8)$m^p z|JCqc4gb~fUk(4&@Lvu8)$m^p|JCqc4gb~fUk(4&@c&Wx|0w)_6#hR7|26Pm1OGMf zUjzR&@LvP}HSk{p|26Pm1OGMfUjzR&@LvP}HSk{p|26Pm1OGMfUjzR&@LvP}HSk{p z|26Pm1OGMfUjzR&@LvP}HSk{p|26Pm1OGMfUjzR&@LvP}HSk{p|26Pm1OGMfUjzR& z@LvP}HSk{p|26Pm1OGMfUjzR&@LvP}HSk{p|26Pm1OGMfUjzRiga41g|Ht6}WAI-K z|F!U63;(t7Ukm@W@Lvo6weVjH|F!U63;(t7Ukm@W@Lvo6weVjH|F!U63;(t7Ukm@W z@Lvo6weVjH|F!U63;(t7Ukm@W@Lvo6weVjH|F!U63;(t7Ukm@W@Lvo6weVjH|F!U6 z3;(t7Ukm@W@Lvo6weVjH|F!U63;(t7Ukm@W@Lvo6weVjH|F!U63;(t7|8e;LIQ)Mc z{yz@?b?{#Y|8?+R2mf{OUkCqn@Lvc2b?{#Y|8?+R2mf{OUkCqn@Lvc2b?{#Y|8?+R z2mf{OUkCqn@Lvc2b?{#Y|8?+R2mf{OUkCqn@Lvc2b?{#Y|8?+R2mf{OUkCqn@Lvc2 zb?{#Y|8?+R2mf{OUkCqn@Lvc2b?{#Y|8?+R2mf{OUkCqn@Lvc2b?{#Y|8?+R2mhad z|4+dGC*c1R@Lv!A_3&R0|Ml=+5C8S>Ul0HF@Lv!A_3&R0|Ml=+5C8S>Ul0HF@Lv!A z_3&R0|Ml=+5C8S>Ul0HF@Lv!A_3&R0|Ml=+5C8S>Ul0HF@Lv!A_3&R0|Ml=+5C8S> zUl0HF@Lv!A_3&R0|Ml=+5C8S>Ul0HF@Lv!A_3&R0|Ml=+5C8S>Ul0HF@Lv!A_3&R0 z|Ml>HC;Z#8-oBz#QeCf4d7Wcj4u(#OGD=DSWNK*9v^Cz}E_Vt%%vz3Vf}I+1Cnut-#lc zxP7g_*NV7(t%%##inx8Pz}E_Vt-#kG;cF$nR^n?VzE_*#pvwfI_#ueJDEi?6l#T8po>_*#pvwfI_#ueJDEi?2V$*E)Qy!`C`| zt;5$ke67RRI()6e*E)Qy!`C`|t;5$ke67RRI()6e*E)RN9rN>ryJLR7aChv(((2uc zW23@9l7{bA95engv)BGz`bBMxDV7@Jo@$9*sp?KL3Fp`lFqNV zWW3AAC@vZC75`&wyu|+t_Mh@EKTWt>@yh6@26ro72^Fu5egbkgPeASt+I)QCHn0>d z1Ixh*uoA2StHBzu7OVs7!FHct@ye+04GO=YD_>9TtfzL?Q#eu zSx@b(r*_s;JL{>P_0-OKYG*yQv!2>nPwg~NI}OxM1GUpY?KDt34b)BpwbP)!=4zsW z+G&Wnb{eRi25P4v=Gtk9xpo?2uAK(;H9p?8(-3p*G{jsx4b)BpwbMZDG*CMY)J_An z(-3#pQP)XoNKX9Kmff!f(X?QEcSHc&eosGSYe&IW2{1GUpg?KDz5jnqyfwbMxLG*UZ_ z)J`L{(@5*P9wF`NbNLIJB`#%Bel~=?KDz5jnqyfwbMxLG*UZ_)J`L{(@5*P9wF`NbNLIJB`#%Bel~=?KDz5jnqyfwbMxLG*UZ_)J`L{(@5*P9wF` zNbNLIJB`#%Bel~=?KDz5jnqyfwbKM|P4LzPZ%y#l1aD37)&y@&@YV!xP4LzPZ%y#l z1aD37)&y@&@YV!xP4LzPZ%y#l1aD37)&y@&@YV!xP4LzPZ%y#l1aD37)&y@&@YV!x zP4LzPZ%y#l1aD37)&y@&@YV!xP4LzPZ%y#l1aD37)&y@&@YV!xP4LzPZ%y#l3~$Zw z)(mgW@YW1(&G6O?Z_V)53~$Zw)(mgW@YW1(&G6O?Z_V)53~$Zw)(mgW@YW1(&G6O? zZ_V)53~$Zw)(mgW@YW1(&G6O?Z_V)53~$Zw)(mgW@YW1(&G6O?Z_V)53~$Zw)(mgW z@YW1(&G6O?Z_V)53~$Zw)(mgW@YVuvE%4R?Z!PfF0&gww)&g%W@YVuvE%4R?Z!PfF z0&gww)&g%W@YVuvE%4R?Z!PfF0&gww)&g%W@YVuvE%4R?Z!PfF0&gww)&g%W@YVuv zE%4R?Z!PfF0&gww)&g%W@YVuvE%4R?Z!PfF0&gww)&g%W@YVuvE%4R?Z!PfF3U96O z)(UT}@YV`%t?{jw3U96O)(UT}@YV`%t?{jw3U96O)(UT}@YV`%t?{jw3U96O)(UT}@YV`%t?{jw3U96O)(UT}@YV`%t?{jw3U96O)(UT} z@YV`%t?{jw3U96O)(UT}@YWW)JNB1}w%Dh^&&2MNy%GFta1;27F<#+g;+5d1 zD9M4pHwJgQJoqH|S#Yy4@p=E6_$TZy3OC|$BOW)#>~SL=H^%I7W6T~m#_Vw;9yj7~ zW85A$#_e%q+#WaLaU&i#2KKlyu*Z#f+=$1G347dx$4z+LgvU*I+=RzXc-(}?O?cdd z$4z+LgvU*I+=RzXc-(}?O?cdd$4z+LgvZTz+>FP~c-)M~&3N35$IW=$jK|G*+>FP~ zc-)M~&3N35$IW=$jK|G*+>FP~c-(@=EqL65$1Ql=g2yd*+=9m~c-(@=EqL65$1Ql= zg2yd*+=9m~c-(@=EqL65$Iq$7jU_&(7H9mha+}YoH5v8IMxov@7y1pR&Ty&nE^ zYDdOb!S5K=E`(CP&Lia;+gu{$8%x15upF!aE5RzT8ms|p!8)*Bc%PWLPxxujdb-tL2$`(WliF{2VOWBe?**$6ZDi5c5p6yA@=`|)@`b#Xr) z@5kf)c)TBv_v7(?Jl>DT`|)@`9`DEF{dl||kN2xCbbdVEkH`D*xD}6E@wgR_Tk*IR zk6ZD$6^~o-xD}6E@wgR_Tk*IRk6ZD$6^~o-xD}6E@wgR_+wiyzkK6FL4UgOKxDAin z@VE_++wiyzkK6FL4UgOKxDAin@VE_++wiyzkK6FL9go}bxE+t%@wgq2+wr&^kK6IM z9go}bxE+t%@wgq2+wr&^kK6IM9go}bxE+t}Quy9PyA*ExP)yHbq;uQ1OQr41t+g|& z*3PV2du*Ln-?hj78~A77pM&c;{=aK1)*fpH|B~_+?0c|VvHwbA!}eGkwpU`bt4-PC zqu?HJFW3$41HEdfomsnfX6@RUwQFbAuAN!Cc4qC`V_(JB0H`-l^{=C#z5*(H5PSyI z7dusQ0{j}N_fl2zEcgv@7#so9;0xf3pjU>s$GqmPJ?0f??J=*SZ&w8H9gT9#tJr>j zq+PwL%U{R#x~BHn_prUXsXg`vw%0hd$F%c^&?{})V}5I_J*Ib5g?dL-=(on&V|qtb zs5hF0+9yKzOVBHG+GGC?dVNlN%x?s=GfUYXo5a2ZUIyRsHOC7;zi-tZF9N-isy*&E zL))3pY>)fR&~|1u+v6qJUfa_izXjVX<=W%#18;LJ$IHNKN@~Dbunw#TKMAhlJgdPq z;GdeSIC_htH|9v&s%x;sY*G z)V2LV>@w^RVV7f9fVWdpiTx4mD(pM3tFb?dU4#8G>{{%PW7lDS0=pjjPVBqD72ry6 z6}Sdm3v%zw^tLC~Q{wez?THQ8UiH+T_zP^WeQIa!w>?3-V+OcALAzrfxIOV#9O>0i z?f-vuXCB^Eu|EDYOVTB6DU`A=0a4bLleTG7K_qQcC>Dy8T|v?|Z3Ai2lSzPr3lwEj z3@ErSAc%m7xL)P5C@v^ocX8v2;&Sz?UKd1h_xH|wCTUUc{odz3&-afXJe_%G&dj{; zY@ahT=Okg%QI;pSAvP0bd72tx7ov=_lFddL+mK-!GP4cquqEr!ZA5o2x&d^9;5KU( zSd%nssp!fRt!7-cHX~u0X_Ab`bzn2Kp)B8(HIPLHvdF-c2C~RN78%GQ16gDsiwtCu zfh;mK$s$9OW5duSiwsS&$Uqhunrst8lPoec*(Qc2S!8IEMFz6Sfb$2LOR~s778!7V zm$GD$0rz+5N){RT1i?TS8OS07S!5uK3}lgkEHaQqh9+5LXp%(+vdGXRiwtCup-C1Q znq-lIEHX67B14lbGLS`vCRt=?l0}9lS!8IEMTRC>WN4B_h9+5LAd3uSk%25SkVOWv z$bdD8v|qBwKo%LuA_Jds7|0?6pL7_=A_Jdy7|0?6S!5uK3}lgkEHaQq2C~RN78%GQ z16gEfl0^ox$iQbM2C~RN78%GQ1D~51nq-loNfsH%B7;a48OS07pQ;$hA_G}uAd3uS zk%25S@HvZtEHa2>k%25Sh-8t0EHa2>kwGMj3?f-%5XmBgNER7HvdDmQC$I$0oun&S zWWf3j+6`G`Ad3uSk%25SkVOWv$Uqhu$RYz-WFU(SWRZa^GN_zK@FuA&6IlfBMWQTO zWWWwc#!D6%un&^5WRbxniwxKeNm;VUfIX3vC5sH$8A(~P$bkKklqHJ{*d<9>vdDnF zl9VNj4A?PAS+dArl0^oSEHap6k-;R33?^A*Fv%i=NfsH%A_G}u;Ik(KS!Cc7C<9q! z;BzPgS!5uK3}lgkEHaQq2C~Rtl0^ox$Y7F12C~Rtl0^ox$Y3}tkwpeRu`-ZF2C~Rt zl0^oSEHap6k-;R33?^A*Fv+4YvM7u!3IkzwL5w_I4Q3P4E268QiJzJ`DA&qox;KqcG3ovV-AB;f zgYI9kc6-r<-)?|3`_Vms)*i$(PoS%ZmZcWQ^S9#eil~mb<(d z&`ip5mlp$?N%>`TUq$x_x^JKh-yaB9;Tx;Kh3^jpSFWsLKr<=JU0w`mCgt~0{s3jU z%ZmZcWc-iPa-f-XlhI8_SMKs+Kr={#G>ZYvq%1!>69bw_S?=;;Kr<=Ab(qT?e}P=sMAr?}WsFZ!kiDZ_*u&ZYgTP@d6~)X1wqKahcb za24n$Wjo6KQ0|ZN87QBL?pYW!5amHA4@P+i%0p2ehH?(dxhM}uSx4D{avsY0C>Nky zh_Vaav(X)i?r3yN&@Dr^Le?GVC#{vc!Whs`%5qm21Nuo>?h0c_zZlR@#>mgb#DIQM zmYvZhaQ0+DH&wd8H0j#3!J7QngN)J$N}5&-z9|ze1wy7wwIr=X$xQ15 zr)Fk6ZQLdmfA)D|l_S?jDluVkCnOFORQG%Z*AMak(}E*Yxi3~dOR zp}|*O@f5HtOqMB`Xr0KnN~Rioa$d+#CvfBmPh@x2o9}v{!qBVv?^R8t{hQqShCz^M&m>_QWN7 zNz@;xvpaNssxK@cm)();(B);Bu`QWj*uC~h*jwjo@`mTxL-lcm-e8@*$=hPD@!7+^ z2ET~-!eFUCXs`8!BVO?5M#Fwl=dYC}iQI$@?F?;H!? z{NG)Rv^4wbe8S%l1k-aHBTa!yKh#iw{wUTn(&)3ho4vK*sVl?m@oMJf>g~(MRJoyW z!|;Gvh8SL1QRf)8v}UbE3uCHAh_!3m z;V)0qH3$5`E7T}Cv|$iC22zKhv;dT2*GfSSLvHEH^86qLVW?I?oDU?sRt@n%80wQC z+@M7vpBK`)A*L2n)PWm@bUE<%$6R)pR8c53fHq5ObZsi;5K5OFY|KSFMN&z+9+-Y+C{4_@UPERuW+MAKT&2TU7Q+1lsg$Z8-d2fd6wr!|AOkds6mBF7{~? zr2KF5MWC*-2W9)nmTrKW$XZF8WtmOTBb8Vi*~sn40%9rwzonW-n*gKFgX340sZo>QztuxG|H(hL zgHXZix*$T)HysU+jc1X4vNlJm(VQcWh4DP$^{My8V)WG1X1sU>y9N9sufX(WC!m&_vp(nNwJM4E{~!bFe=iIVwb0a-{|$Re_sTud$@ zmy*lK60(%El4ay_as^pVR*;os6dko*|ZDoO1shSv)wH&!7Y7ne;3=kPf1Q=@2@U z4x>3Vmky^obqQ&%VI)aX*=g?8~TsoS%X$dW*Wz<8<=@>eeR?uxtI*m@JGw4iu0flGcbT+++&Y@mfLu+Xr_0f9TKpUx_ z&ZYBcfHu(}4bf(5&@dG=LZfs(T|gJo7P^QorWeyo=%w^Bx`ZyJt#lc^oL)hf(-m|j zT}4;ZE9q5q4ZWJi=vumtUPG^?>*)r19lf63KyRcs(VOWl^j3Nsy`65Po9G>MGu=Y( zq+97-bQ|4HchI}(PI?dBMen7%>3#Hm`T%_pekcB6_`T>y=%aKG{3h#Ox{vOs2k2w; zae5Gbhx1AJMa!q@A^Hq`7Jk9;Irv4v=jjXdMfwtbnZ80_rLWN=^mX`Uy*J_4=H7;1 zZ2LQXhaRKv!ta_Lhu^#UfPM(S0Q3*~G5v)85B-#WMn9+jq+if4=~wh?dV+pKPttGc zckm77-_sxHkMt+{GyR4B3g0=ZF#=yJ$>1CIEX)euik8e$SSozSRT_iuGJ9?+y0advC+h{DkM9GY(YCXGtUo)04Pa-ov)Dj3hz({#*ibf%<*-~f zoaxNL@>o7AU`|%ZikOQPv$NR_Rq+&1M&|Iq=lJhSjn<=7T5V4e-3$&*rju zEWnyr5T2tn!|7v~2^L{dHlHnE3t07$>^62g+sHPtJJ@Enh26=v zvb)$eww>)@ce9=B9=40!%XYK-*!}DQ_8@zRJW*x%Um>;?8Bdx^cwUSY4Y*VqyEI(vh?$=+gbv!m?q>>YND zz02NX$JzVr1NI^Ni2Z|o%syfN!#-u7vCr8**%$0f_7(e@onYUvlk8je9XrLoXFsqX z*-z|e_6z%!{l+y;IOU9UZsAs*#FKdnPvthA#?yHQ@4z#8N8X8N@yOx-U%{926?`RM#aHty`Bi)kznaJRTE327!>{G* z`38O+zn15op0ou_#J#R-@@S z>MSDQ6^(q6FC1c_ppQisge;N9un*cV6bfqT|Km`Z07U^*xUttO(AT7)Ig}gU+WFPXiAC({krZh zOKsb-rG)0gu#k1P*7=|hU`RlxLpf1lgKia3?D23qc5ggn@zzEoKH3zOc&^k6 zOe2R|Y6Yf~Vuy;hv@)Dt5l=5e%oAy}PC)h6DpN(3siLYao3+ZcuPUB1xhWcm_?rVQ z)+!vO)+uJzDQ4CwZCO*M#Pe8Z;6=;i#!xtz+TaT}!L+Uk2&?Rh`97=H%co7yaHjCGnTpMo|=zW>lXJ+=bWln*vG>4njZ>I5^Y1I6Y?VjR~r(r&5hM?ID zAv1Z%Ode`0(i$@D3B_|+>-_Wmbv|pzY=o$pF=}Rvwq;C-CUUgkMc@uJLP|?KI?3JS ztqq5QNnX>px?#r2HbF1R9cqB#H806)`qok`#9C`ADs59_t8J5cXPv`89%Y?RS?4he z_MvAR(`J#ap-r}qF-vYhkB^bIHh_~h2FYz|No!~qu#IiYZEI|k`B-2KZP zHn&YqJFlJ5Y4c7CNK^#_Fz)@e)=IMz1L&nywoeym7qC{E%^5(CSIUM8fMcyR2VKDQ zCYYrK&C({cDF98;zug+J|VBhlYICNv0)mV*%QxO=_n+E!-|(on%@PHoa;ymq5=}-PW8o zxaGR>0^Nt!eB%Cl=d}GkG2mbO;HmfYWlWmZ8fkhXeZBTC%3f8DKp|& zu+B7FWf6L*GZRBHbx}gJ&NOSb2t5m|R2qb}J`e&cQ}Hfh=0$R%nB+F^AxT~ZO%vgG z&1RAe<+SQ{?Ux2OTUb!3$=_zH#Z+!Kmj#xEM1sChk}6(cQ}pVvTgOM|SWrOp?Kc#~9Fup)*k z%8PIW9r1Emm}MST4_4_=J=4&VQW}iXh5n?Fs$;XCg&RXwShhEL9TxOh1gfe`V9ij? zTKEHtEFswkX|m+FWgUKJX__k5>_Bx91F4u9#T0M7-w((CdHHe4=}1U<390RBLAuJ} zbjp@ZgbHOSk-jJ)xe`)wTq@KPQbJc@T$iK38NcdCl;TK~;z*Q|mnbDKQA%FC6g{sX zUP@k~yu89hT%zQ>M9F!HlJgTK=O;?ePn4XWXp8)Wj{Jm<{DhADgpT}#j{Jm$WfTcQIxQ%C}B%c!j__h zEk%ivixMRlB}y(zlw6c3*_9~Sm8h>Pp~IEX;Y#RmC3LtFI$Q}Iu7nO(LPv2zM{z<& zaY9FNLPv2zM{z<&aY9FNLPt?sDRCVzQ`8u8=<%x#J+8x{$8|XLxDJON*Wu9PIvjdj zheMCo!=Wd1I1)PAY<9-u^kSEFnz<}qV0zQ==3-rdUdpsM4pJ7xCF=|`VT#GSyyp^0}e2RB(o_YVavGj|#4o@mIpX&Q z>iqTfmNL9wG>1cV(b`Cgh{8QYT5Q1`cM?2km0R^>f3O~Q@{tx0B643Au)$3v99oD+ zCCTz`F3(I-ad8n}htJK#g-5$Z ziLIU7v7H>2w?4z(CHZEFdcIjA`1!ms6q+Zyti~4zEx=R-Dpi4Q>ML-X_7^x+xKM=)&3Y6Rn)N6s zL@V@yLTpFyu^qw3`hkz_2tKwW_}Gr%V>^P6?FhcvP6dT=eb_D#kL{ut6e;~hN`H~k zU!?RGDg8xCf05E(r1Tdl{Y6TDk(qE+XyOe&H((h9GT}r=8>31pp zE~Ve4^tzN@m(uG}dRzp^tn|%-Kw5$rQfaeyOn;o((hLK-Acb(>31vrZl&L?^t+XQx6=p-kCOrfeuvHk2tF%9IUdsvXKyKX{aWk7@&t zY6Fkb?@{_aO20?7fk)~0DE%I#-=p+IARcRI}WcRI}aI~`{IoetFR@R;)y ze7p`mUI#yZ9qofK+6O+`2R_;dKH3L9+6O+`2R_;dKH3L9wh#DdANXjW!(+}<@RfdZ zoTk|b2&?*+7Rk8=VOM;vfn2QO-0k9&9p+qxG|G-r)efb~4s)J5Jmx$FU)f>KQwS?N z%y|l7WrsOWA*}kroTm_0{b0^h2&;ZD=P87hesi8WJmx$FU-g4IPa&-O!JMZMR{dbk zQwXbmFy|?RRX>>X6vC?h<~)V4s=ql;A*||e&QpiSoTuQc`kM0+!m7UJyo9i-uQ@OC zbah|I*UPioj<0YN0*l3(c5%4vka3;b(#dtq?~>tpmW<25g=wNBGngnO9k0gon->#V zGvjMpd0lQ2I>sYv;OQg8O)33-Ol4^@EaY>W;gM;QT+`-;gjGg>2M@l$OUBhb2uX64 zi#{xH<#kxw%ImnSl>)A4WdgaF1)tW}%iB?&BTrAB96xA{D8bSohir#-C-^d)Y98N9 zFkoO+m7RD#kdrC zVulAoTuN!uiEkg(hF5pSH?q1DM}}Tt&Sdc8Gh2<2Qnvxb z5C4#F5auhv94Tmx4bo?59pMe(u38U_PX!CC@P=_3SkXc2q-AT}w4UmU1>Q)O_EWGV z3+(O=HuMho8@$?XbRR}{ZvY04b`afX(0w5gststbq5F17UVRVUPtg5J4m9n1bbpmD z;X-z4D!QG}?IDEjApOxDf^HtTdE{(#N26N~Za%3*cM`hOBeJyF=+>hf#P*TzHOn`x z+uS6S+q=maBVBln3)D9+R$o8&LM$f9kjOb6cOwID(~^E`@iXE5g+TPKD zutggO|26Gh_)p+X^5YQq0sLp$NARCRC>O@kFzsqBrmfZ1X`gDJYhP$zX(zOk@Md8H zyd$`Y+yQT4y$Nq5je*nz-uu~(wd30TA{k1{x4HXPtC9Q(*tB`&reBy~}71RlC z6)ga_Qfh}Q@}HE<@t|eThHz($)v~ARt=W^TDMOZzU4AN^B+-qn*`*M5E+xca(^IUL zp&5+!uxPs1nmW`9ub;LSQNlM?>*wpk+Qsy`qtB9F+DQCg1#ck>c>h(tgDu}b9wq z?_Ifk&fqTNE*O5^x_K+sedC-w-TCRug|lnMPddNAIJmgJ_o`>!8S>=Z)bEPkx*@Xe zyK8pserW!6UvD2*>lu?f&vwm(^M+pa#q@DkzLxZ1k73`p?wJ18^CjQpJQh8+=I%pR z_r57mnY^LTv7=u$91kC?IC$Hfk;mWP-(_>{LVxGQ_vBCpgJ|>8C-tR|>jlFducv(JQhWheh`Vf84 z#Er-iO|_{JQe!*X!0jFtx|%t)E@9-&MRjS{OO|hN=&X_T0GY@P?A!yB|HY zam}>%emqjgOPmwP%lUV7eP;ZQfrm~UUtURF zf1ve>C*~Y~_ntv-9=h|qy0;JYykOtX;^ZT){+sfz9KQOd&-NzuJGac8lJt{o(o;v9#-=;A{I1FLrB}9_2{YY}Y&}ahadKALG&slgg@g7| zZ?n(QS??sHld^1O(Qu77IA6Xu74MJKEbHV(?}CWW(Od5+li9L5n-TU>IH~s6!%?j~ zyLI%{dz<~&MO~H?Sj6YAnzs2cQ{l)iK5b<%KR#{$Pgv1P`nPWtIbB;x2TgA!sZ@@r z?T7E*^0(*h_oiO5YUhgRR}W73`rXGn9BA4S_ppO5Z!>7(h#&OP+tyDRoxv}mp)m)*EDYugz63l5P!E$8`#1^F8~-Pq~T zV~xXi{NsaztFIdJ#FhP4)bCq1eOf4bVC0UoR?Iq_nLYCM%Rim`cxv#;U!NNH&Z9{k zukU~H(NP0m?z8ao+a1q-{g3|Lk3RXpn9`eiT(q&z+7D-c`^CjyUvhU1iG4fXcI>78 zQ@3q+e%H$RyS~_y{>_Ku-`e<72 zXHVC_Q|FG;TlvrM=Tr08H|eb7_vdyyKBL3nD|Yt(_`=^=tY6e_8vnbyXp!!O27wux zTMDnH3VXhNVzdSxb=W6{;h{~q<@6bAcSq%GWAK0l@2JJPGY=l{#iynW!gF!+j=J>W z|Ih*j0b5p=wp(gADkAm?KG;r%=Q&2`#iogQli`g$AwNqO6+XK+VwZOop*{)M{+2X6 z-0ZavsvWEsgLSzwGs_y7C_P@$(oh|&7T_)>7Yp#Tq&O!}E*1W_>A%0B_k(kGz#9`& zyIx%X?Cj;~N50;9m$B^XD_4CFFED3Y_vBpOZ^MT#-!ifCr&s##d;7%w&JlfLPo(_x zb5`!m1FJ4RXT+I(zWZkS;#*FVhn`M-WXTKr2S&UN1IN$(=<}uC=`EYz>zdgqd*JTl z;|neuI>ui1=&||%^{bz++SB(b*E5sX9uI%ly*@hk(Tc_wT`lQ5^le9n%^7>!8#_9a z?aSV9t<#IMu489kJbLoO_q;c}=d!o{o?ec+9uzw~?ft_wGJux;O(IWeOkcl=#1 zJUjc$*RI<8P4MEud&#Vy9V=!Yx$26B=_|W$JmCE5)1jODeZ2O&Wgl((=bVMJ7tdMt z%QV|8cY5vQIj@)Y`_i2Lt>h@Q)Ejsk`2AFFmQ&2m{d$A$v)bk?A4`^h?;%NDnLbq? z6u-Mu(xunG?7!M(p$|!5>AY zy1_TR!Cw!n*S>m^S`72fv5#Kuv3%#6tLHuO;*z*S=)D|0^llIVW4|K~`TWuZ;GCw(8St@!&ey**Z`)bT&ult(ec$AqC!IHM z+0^I6?`R_VcO`r5o%L_J@9_F}XK!10NPl|m$V*F34S4v&Wuhy*%b$76ruTnbI{dT8 z4wro#esAK#Q!?$#g4bR1;Z5hyeXz*!_d9bwyGlFMyXxcdd3&z-e!~7A8>aZan!A6| z;W}53%*=(J`HpXvFJc|$9DHEbt>0Z)Kfkr-jN{8b+E;O>YwAbG+zT?MchFlcS+I`z zN%61#gRdG|4rh01nphoL*}ccI-&w5xe~Ta3sQ|Q!@`~HI3hx@|t^`+KI-RR>&_O3B zXQht^e#2*XfsK)fh_A(xvaHiWHSoCy2aE`rFtu~`czEeeeiqmc2VD&kez>0+rH_=? zZCU0AsJ#JuMVUR$${hQ6`Bs?-)ko~r;4FZTKiH>w1Ah33HLT?9^Ya|ta23yOojXjH zWy43I&5u=!xMub*H2>S}(fYED8~hhfNq^~Rmo<6L)s^{o&$e%>d}GK8*Bdw7Ir^*a z6}J9;s;dr`ZasYE_NiY6$6B{pvWxrFju_Bs?V~rmcsSX4N%(^?(|&1rZuPj2M>pp< zYcJci5GI^;lKSzh#)yATY&gJ>>eUGTkhpmacz2y91 z>XufQ#xLRw#!~ENDW{_oI1oq5zeT6S;h!vom>!L1hs~Wt^MAIvJ2#$7r+-=_uitw* z7Eh~@FRb^K?Q#Z((Xr&uQPa_yV%{Q3z%bb6@k|&}~(C z!?h_d_3l2ktm_>szxip+t-DU8JCBV_+gY}Lj%Uctzup|G9C2SkLv!Zx-)0tHIP-%* z>)pnm$JZ>H_t1i}eQEccoOR}~51wDGk2;Ut(sFp~gk83Se;bfwdwhNC zcbQ-BS^MzKH@$h`$gHnFrPgi($B$eU%3uBJ@?-xqJwJNRXV?1QFAGi30xur9;_azh zZ`yk9bCvxX*PfWO=lCbqS;hUb^8V>)WetGET1tq%^tu0Uu}3ri0Q9zVs*TNhX1fuQ z=8)$M-4mxqDa?`9?lK3?wGAJeBLfa7+QQlT8EqN41MYugKlt=$*V@?A_hnBTX#b1v zWcTU~-P(^d*#GgROFSd?zP9Jfw|gF_8F8%B=i{uGdkpup^w->s4d1M~Z_-KkXH9v- z#vT~FZtwlWXVncT$lY_@gAb1r*T47O?e0^j8Xx|4iFKiSfA!kOE?qe3f?hi^`jz~B zf66WSua-|-JHm2f=Fl1b&#!p1_T`)>tk)I%v-9R#PJBIi^pP=LH`=tmSJi)cY*f!p z5|eI6pL_ACTOPk`@wIa%^n7yYv<>T@>vH_2hWwX@4$gY! z^Zk2g+{d;IzVY+kD{maIVnK(S4|o3Qwbv>tBN@+}lN+)>%bx$_qYf|iD}G>V_ai^; j+_!VPYv1l8X`A1wy=V7^2OFn9@%|fwe_OZZkf!}Vb520^ diff --git a/assets/themes/natuurpunt/fonts/SIL Open Font License.txt b/assets/themes/natuurpunt/fonts/SIL Open Font License.txt deleted file mode 100644 index 14c043d601..0000000000 --- a/assets/themes/natuurpunt/fonts/SIL Open Font License.txt +++ /dev/null @@ -1,41 +0,0 @@ -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. - -The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the copyright statement(s). - -"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. - -"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. - -5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/assets/themes/natuurpunt/fonts/license_info.json b/assets/themes/natuurpunt/fonts/license_info.json deleted file mode 100644 index 38fcbd7250..0000000000 --- a/assets/themes/natuurpunt/fonts/license_info.json +++ /dev/null @@ -1,24 +0,0 @@ -[ - { - "path": "Amaranth-Regular.otf", - "license": "SIL Open Font License", - "authors": [ - "Gesine Todt" - ], - "sources": [ - "https://allfont.net/download/amaranth-regular/", - "http://www.gesine-todt.de/" - ] - }, - { - "path": "OpenSans-Regular.ttf", - "license": "Apache License 2.0", - "authors": [ - "Ascender" - ], - "sources": [ - "https://allfont.net/download/open-sans/", - "http://www.ascendercorp.com/typedesigners.html " - ] - } -] \ No newline at end of file diff --git a/assets/themes/natuurpunt/information.svg b/assets/themes/natuurpunt/information.svg deleted file mode 100644 index a0b14a9814..0000000000 --- a/assets/themes/natuurpunt/information.svg +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - diff --git a/assets/themes/natuurpunt/information_board.svg b/assets/themes/natuurpunt/information_board.svg deleted file mode 100644 index 8d2d3c0d2e..0000000000 --- a/assets/themes/natuurpunt/information_board.svg +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - diff --git a/assets/themes/natuurpunt/license_info.json b/assets/themes/natuurpunt/license_info.json deleted file mode 100644 index ca5cbf4149..0000000000 --- a/assets/themes/natuurpunt/license_info.json +++ /dev/null @@ -1,148 +0,0 @@ -[ - { - "path": "bench.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "birdhide.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "drips.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "information.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "information_board.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "nature_reserve.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "natuurpunt.png", - "license": "Logo; All rights reserved", - "authors": [ - "Natuurpunt" - ], - "sources": [ - "https://www.natuurpunt.be/" - ] - }, - { - "path": "parking.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "parkingbike.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "parkingmotor.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "parkingwheels.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "picnic_table.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "pushchair.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "toilets.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "trail.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "urinal.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "walk_wheelchair.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - }, - { - "path": "wheelchair.svg", - "license": "CC0", - "authors": [], - "sources": [ - "https://osoc.be/editions/2021/nature-moves" - ] - } -] \ No newline at end of file diff --git a/assets/themes/natuurpunt/nature_reserve.svg b/assets/themes/natuurpunt/nature_reserve.svg deleted file mode 100644 index 96c2d26ebe..0000000000 --- a/assets/themes/natuurpunt/nature_reserve.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/assets/themes/natuurpunt/natuurpunt.css b/assets/themes/natuurpunt/natuurpunt.css deleted file mode 100644 index 09486cc70c..0000000000 --- a/assets/themes/natuurpunt/natuurpunt.css +++ /dev/null @@ -1,111 +0,0 @@ -:root { - --subtle-detail-color: #007759; - --subtle-detail-color-contrast: #ffffff; - --subtle-detail-color-light-contrast: white; - - --unsubtle-detail-color: #b34f26; - --unsubtle-detail-color-contrast: #ffffff; - --catch-detail-color: #FE6F32; - --catch-detail-color-contrast: #ffffff; - --alert-color: #fee4d1; - --background-color: white; - --foreground-color: #007759; - --popup-border: white; - --shadow-color: #00000066; - --variable-title-height: 0px; /* Set by javascript */ - - --image-carousel-height: 350px; -} - -@font-face{ - font-family:"Open Sans Regular"; - src:url("/assets/themes/natuurpunt/fonts/OpenSans-Regular.ttf") format("truetype"); -} - -@font-face{ - font-family:"Amaranth"; - src:url("/assets/themes/natuurpunt/fonts/Amaranth-Regular.otf") format("opentype");; -} - -body { - --non-active-tab-svg: #556c5c; - font-family: 'Open Sans Regular', sans-serif; -} - -.layer-toggle .alert { - background: unset !important; - padding: 0 !important; -} - -.layer-toggle svg path { - fill: var(--foreground-color) !important; -} - -.layer-toggle .alert::before { - content: " - " -} - - -h1, h2, h3, h4 { - font-family: 'Amaranth', sans-serif; -} - -.tab-non-active svg { - fill: white !important; - stroke: white !important; -} - -.tab-non-active svg path { - fill: white !important; - stroke: white !important; -} - -.btn-secondary { - background-color: var(--unsubtle-detail-color); -} - -.btn-secondary:hover { - background-color: var(--catch-detail-color); -} - -.layer-toggle { - border-top: 2px solid var(--foreground-color); - margin-top: 0.25rem; -} - -.md\:rounded-xl { - border-radius: unset !important; -} - -.rounded-lg { - border-radius: unset !important; -} - -.rounded-3xl { - border-radius: unset !important; -} - -.layer-toggle { - /* The checkbox that toggles a single layer */ - border-top: unset; - margin-top: unset; -} - -.filter-panel { - /* The panel for a single layer, containing both the toggle and the filters (if any) */ - padding-top: 0.5rem; - padding-bottom: 0.5rem; - border-top: 2px solid var(--foreground-color); - display: block; - border-bottom: unset; - margin-bottom: unset; -} - -.layer-filters { - /* If needed, the panel which contains the extra filters for a layer */ -} - -.first-filter-panel { - /* Additional class on the first layer filter */ - border-top: unset !important; -} diff --git a/assets/themes/natuurpunt/natuurpunt.json b/assets/themes/natuurpunt/natuurpunt.json deleted file mode 100644 index b3e7176165..0000000000 --- a/assets/themes/natuurpunt/natuurpunt.json +++ /dev/null @@ -1,338 +0,0 @@ -{ - "id": "natuurpunt", - "customCss": "./assets/themes/natuurpunt/natuurpunt.css", - "title": { - "nl": "De Natuurpunt-kaart", - "en": "The map of Natuurpunt", - "de": "Die Karte von Natuurpunt", - "nb_NO": "Naturreservater", - "it": "Riserve naturali" - }, - "shortDescription": { - "nl": "Deze kaart toont de natuurgebieden van Natuurpunt", - "en": "This map shows the nature reserves of Natuurpunt", - "de": "Diese Karte zeigt Naturschutzgebiete des flämischen Naturverbands Natuurpunt", - "it": "Questa cartina mostra le riserve naturali di Natuurpunt" - }, - "description": { - "nl": "Op deze kaart vind je alle natuurgebieden die Natuurpunt ter beschikking stelt", - "en": "On this map you can find all the nature reserves that Natuurpunt offers ", - "it": "In questa cartina è possibile trovare tutte le riserve naturali offerte da Natuupunt. ", - "de": "Auf dieser Karte können Sie alle Naturschutzgebiete von Natuurpunt finden ", - "zh_Hant": "在這張地圖上您可以找到 Natuurpunt 提供的所有自然保護區 " - }, - "mustHaveLanguage": [ - "nl" - ], - "icon": "./assets/themes/natuurpunt/natuurpunt.png", - "startLat": 51.20875, - "startLon": 3.22435, - "startZoom": 15, - "lockLocation": [ - [ - 2.1, - 50.4 - ], - [ - 6.4, - 51.54 - ] - ], - "widenFactor": 2, - "defaultBackgroundId": "CartoDB.Positron", - "enablePdfDownload": true, - "enableDownload": false, - "hideFromOverview": true, - "clustering": { - "maxZoom": 0, - "#": "Disable clustering for this theme" - }, - "layers": [ - { - "#": "Nature reserve with geometry, z>=13", - "builtin": "nature_reserve", - "override": { - "name": null, - "source": { - "osmTags": { - "+and": [ - "operator~.*[nN]atuurpunt.*" - ] - }, - "geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, - "minzoom": 13, - "minzoomVisible": 0, - "mapRendering": [ - { - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg" - } - } - ], - "=filter": { - "sameAs": "nature_reserve_centerpoints" - }, - "=presets": [] - } - }, - { - "#": "Nature reserve overview from cache, points only, z < 13", - "builtin": "nature_reserve", - "wayHandling": 1, - "override": { - "id": "nature_reserve_centerpoints", - "source": { - "osmTags": { - "+and": [ - "operator~.*[nN]atuurpunt.*" - ] - }, - "geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/natuurpunt_cache/natuurpunt_nature_reserve_points.geojson", - "isOsmCache": "duplicate" - }, - "minzoom": 1, - "mapRendering": [ - { - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg" - }, - "presets": [] - } - ], - "presets": [ - { - "tags+": "operator=Natuurpunt" - } - ] - } - }, - { - "builtin": "visitor_information_centre", - "override": { - "source": { - "osmTags": { - "+and": [ - "operator~.*[nN]atuurpunt.*" - ] - }, - "geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/natuurpunt_cache/natuurpunt_{layer}_points.geojson", - "isOsmCache": true - }, - "minzoom": 1, - "mapRendering": [ - { - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/information.svg" - } - } - ] - } - }, - { - "builtin": "trail", - "override": { - "source": { - "osmTags": { - "+and": [ - "operator~.*[nN]atuurpunt.*" - ] - }, - "geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, - "minzoom": 14, - "mapRendering": [ - { - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/trail.svg", - "mappings": [ - { - "if": "wheelchair=yes", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/walk_wheelchair.svg" - }, - { - "if": "pushchair=yes", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/pushchair.svg" - } - ] - } - } - ] - } - }, - { - "builtin": "toilet", - "override": { - "minzoom": "14", - "source": { - "geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, - "mapRendering": [ - { - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/toilets.svg", - "mappings": [ - { - "if": "wheelchair=yes", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/wheelchair.svg" - }, - { - "if": "toilets:position=urinals", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/urinal.svg" - } - ] - } - } - ] - } - }, - { - "builtin": "birdhide", - "override": { - "minzoom": 14, - "presets": [ - { - "tags+": "operator=Natuurpunt" - }, - { - "tags+": "operator=Natuurpunt" - } - ], - "source": { - "geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true, - "osmTags": { - "+and": [ - "operator~.*[nN]atuurpunt.*" - ] - } - }, - "mapRendering": [ - { - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/birdhide.svg", - "mappings": null - } - } - ] - } - }, - { - "builtin": "picnic_table", - "override": { - "minzoom": "14", - "source": { - "geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, - "mapRendering": [ - { - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/picnic_table.svg" - } - } - ] - } - }, - { - "builtin": "drinking_water", - "override": { - "minzoom": "14", - "source": { - "geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, - "mapRendering": [ - { - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/drips.svg" - } - } - ] - } - }, - { - "builtin": "parking", - "override": { - "minzoom": "14", - "mapRendering": [ - { - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/parking.svg", - "mappings": [ - { - "if": "amenity=bicycle_parking", - "then": "circle:#FE6F32;./assets/themes/natuurpunt/parkingbike.svg" - } - ] - }, - "iconOverlays": [ - { - "if": "capacity:disabled=yes", - "then": "circle:#335D9F;./assets/themes/natuurpunt/parkingwheels.svg", - "badge": true - } - ] - }, - null - ] - } - }, - { - "builtin": "information_board", - "override": { - "minzoom": "14", - "source": { - "geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, - "mapRendering": [ - { - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/information_board.svg" - } - } - ] - } - }, - { - "builtin": "bench", - "override": { - "minzoom": "14", - "source": { - "geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 12, - "isOsmCache": true - }, - "mapRendering": [ - { - "icon": { - "render": "circle:#FE6F32;./assets/themes/natuurpunt/bench.svg" - } - } - ] - } - }, - { - "builtin": "gps_track", - "override": { - "name": null - } - } - ], - "enableMoreQuests": false, - "enableShareScreen": false, - "enableIframePopout": false, - "enableBackgroundLayerSelection": false, - "enableNoteImports": false -} \ No newline at end of file diff --git a/assets/themes/natuurpunt/natuurpunt.png b/assets/themes/natuurpunt/natuurpunt.png deleted file mode 100644 index 1fd1486a52c877ee4d61c7ec163d60360212081a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11034 zcmV+#E9KOQP)H}*BJhKOuic+ zTrC>hrI)KJ_v|L&;4L*(y2|5!Rk>H+?}bB;?)muQzkF5x>mJ*}($!AA{{mkv{hS-h zJH)~w4epLoe>Eta8WZbgKOB4l0<~LSIB=&MR_=Dfp&i`je@~U}*!ywiD;!n$gGpXb z4c%luCcga`@9j+V`EJnr>FwL)u}HF0k z!ygopg<|pS! z*X4D%OFiJRo%oVQRd>q83 z8i09nRq?u7z3>gUk=p=~F;k=qYu2oJqgWhpc+R>V%U8K!MRlT*EC@j;g%A)DBBElD zLFb;+B{`2r4N*==jq2_uVg9_0!4W6As`x{cm&a&z=CN9NF-}#LpNJpxCr+^V{{OO_ z*CA)EvFiD6ZQd-e7K_Yd0l69OI6apv+?sZushAK8HPlWTmDB{9COL0{;HF}tdR{m@ zNq82K=R+*WnXCe^4dU?{UNrc-TA2S0^-^Dz|L2F5%aTTo?}WLNUGhS)$Q%|pQSWga z&}Kv-XVavbt3_BgT|BojIh2Yd#%R5G3S>nIHQ~%fIzVawnIWtuEh-KvQBS^;X`)c# z@rx?GVv!9T(>Uf}FC4tMDM27)2Wm&ys4k}K z>AW{A|Cz@_SR8(`*zZG9l(=5_krN$$5~VV+m^Uo&8@=PxB?|{MZ94}XW8#MW|IlH7 zCKg5qLgDfW(Pv-$;kQVPQ=d< zPcI(gnx(4b!idq{+_FVpsm&dqG3`Bd{S>o>%F?+aZ8)wjM~cN9V3)kWt1tO#tFlm9 z7>9Ui28ePE3d-wJTl5=C)=m5>-$@&Ecq!_1#)jplppk*iPIlxymO^k%pRBwa>8uQsQW62kFC+XCQ#nt88 zmmhxG#K1Dh6)WI{x?b8^Im=g)DxkV_l^`qmDz6bg|6^h?^QfL*%vDvnp_QYZVZnqz zMpKDUDGr|AMqWnVDjk3@+p5=&`l6bI?R|_4FWPvC44&EOE0dR^exwr3A_YV?O&4iG zOvKH6SKL2^dj4_}$AsPL2Zb%97Gg1TASCUNdqzpj8e&cLd}Zb3E8iv^!c@*ASx_go zX@y2rXQF$9sxqNO?UtOA)WobTB-NYGKox-e8d8!DII6In&j)ESR3p*w*TiD~;Yaxq zeE>RLC@cqq=hN{acOx~B{Q%@_Rnyc9=f7!_yhdgiOeodVveS5dzMomN*Mx<@+=r4f zbmFlOf?VX28Zef}FiE5TQ)032VDdbTcqjo5yA?T65T?(+v~AT}#lTFH6JoMV_fH-> z+XK<$sYC@jSi1hn{}7d2n*GCmqCDmVg>OP)lvL-Z>nB$&7mLn8D~_GHFN#T?b?nxB z!JLDfO>%0ZVuo~#o6G@Dp6V~*%t?DFN#W=T;}|l^NwhmXRk#QvNX|}b0HHZ?i6*vC z-yt}o_l1;@!V8Kgi-FlBC#HuwBJYb@@>dthnIMw6!y-lj84y9SQBkI+AT7Bk#N{tW zE!igJQWS<{#G*Y=JPOw2PPpq1OwU%@m@>w z+3+$sq09p)xj{}{N+WdB(j=r4;@O`%=hV~o7$vF#Vv+7bH-x$KYbyWav7r1t^38T# z7}S9Q3YkM-F3iz6lqZ>5Fs>)_2r3esM0{49I|OCy4sz=^Ix|U6TlAjiGxRVB!U>~C?(_;17`#qn_+vbMV<$#a}^Q)6ij}rl#a&Vj;rJVKQVlBj)&Iah=RF zJayy;%NhHW*oH zwQ-R3a0lU#1DtGIvbb=oeT0XeGFTl)L8s(5qYk38+0){c_yPqlsYg!k}*(M)jx=!e=LCmdZ3V# zHYGExIx% z4J0i|8B#W# zH~I?x)O@AIX0N^WIp<#Ioa6nImhQRx?6Zfpe(N_6jYW<9v+MwfT%z2K1#ELLkW1@W z*>W|2g#RH@Ca$n-IkbHLyF~qKAUlVH38ljgo;!E+FUHcI_deg~8{a732{FeM34ZIc z zeD81BTey_Az$F%W%|v+VT|rLyNiBq|OD0NZE;|G?af%Pt?(fi?R5x1K+rqR@|5y1Q zg-s*3<>eFPy6rw(NEdNrYMl7{4?I+M2vqB9HCwPgDO$UOTwyj`D`cxSyzd%YmjmuO zdt(=Cvr~-5lJwF-F(Hh^n!W4d5rg|wzaPp{*;07@_3~Z(=yUSscd-^G);?Ct)ViRc zDu?iVIi^@|*(VS@&?qbYpL*(SvsVa6TR=hf6*-^#m-6E^Qp(6w^ovcGZ{26!B5a=P zx$F}NzZ&yG>{2+ENTfPyKicj8k3oIwFPGheYp`%^+h1&6w+|Y+;2a2wtqT%iZ7KT% zUUsaG{R(*)0t$EuUj)=na)KGz{Q}=gO4qW~fKxeg?|p z88Wt^(%TpQ=|i_L89`8Ukw9c_O);$I26Yu3Z@$XD03@^vV>`=2YF$ze503odmw)aD z6>K*RLEz~CV<&hn$)I(vQ{S`h z<~z65b^2P8(PA?5Hwx3LBjN)JEaijTr+OU9b@G)v|BgAw`<{N~_Q(CT`606T7>wEA zK9oLlmcB_ihtF6&jXk}ToTsZv*>#YTktP9~M7r=WsZgvA(dW**>kr}vHJM~0$TUP4 zly3^b)N;iDK|ARzmJzB#o`G;<)bNQjPki4~uRZ)T zD8pyZ-Tfzo@?N-_iOXMXw?+AskZ~=;Gpt%;K2-Ct2DVDvF$bs4-+9tA+C)>&gwjT= zm@9_hEU2**Z9PMYK#rl1^1CvBuIrfj@*H9HI)0R02l8GEnV6EEJa_X)$T7S~*hsG_ ze<%iO(nFN6*w&c(J!(A0@}tz{`-2{A!Z->Atk*##b0KXcMPbLpjUhEh9bH@HIaD&; zH&Rwo@1k%5NNTNW`?NXGTeIvspqx|kTTB*fQkiegrAA}FCCjxuJ##!lIp-jUTJoNE z-6pWZe4v_JL1ah|k!>!TI0Upw)4K?PASRd}F`Tq>8ob{^Oy4Q?wHL;Clygk>^P*|T z%#G=9XTrNImt17hTZZy<;N8aIIa>V=TWZ7^6x1=)(O+u=p^f@C*3o7l+j25MVC`gfve<%-7=-LBSlE-7s;R};=TN&T zq_r>>)MT0HDQ$KW)6OO`XN>t%G9od=a|y7RBg&*2aot*Gc+aU8i{>kGC^jvt66;1G zU6+{P{^pm)o=pIu*;fUNYt5MN^wwnVD!T$)a}2|Fnn?&{@!5;_CegrBEX3FZhmAu{ zlaod3QKotU4dRO;YE3bsC^4=(j|`f$ zVF-=&Lr%^OEWW;HYV(&}09llbSOcGMp||Oo;ma22iK)e zWF9;CFbvy}d0UWjXwvF$-oBDJwV@FSG9tv(SDrhW*(k`!cMd_*+5{Q2D^9Thlm-Pi`EwBl1z*?vlkZwig_IgZIp-Mj z!~RzpksS?OgQrdF7m@5kQ&nY_Iv2}^XOzBydQPni)6yPoT7n!wytfCY=E(T=@81Z*GS;py!#vu z#F$TUp=|N^JawrTr_LJKbSQ9)>^81@nkV<|IWr<_DlK&E;<4{7y9DY}n$Y13g<`gp zmavm`d#n2`T!WZj9BfgOpj^ItN~0te_EfGKY~jg!h9eu<)64Z~H~En_y+*_-i-) zlE2~vAj7v<;%o1CzX)@z(KvbjzCZV!;kV2(s85dRPpMXs&wl56r@!)^JETp!=Za>V zYXnqt9}6H6W(TRIk+4Cl_nzlKUCkc^#c6sQ3s)+;3?>wNX$4|H(#SlL^)*)|d}o|I z|L(tJ9L658pJ?3v8YWOXX4#t}$xmG@4@lu%`iHL$I0mYke4&Ul!(fm6FRrxgIvo4z zgSV2QrsO-wxihASJq02Q#3U+^g?mw68o8Ez0Nx_GG__2YooSbnW45Wjkh+!FVQ*+1 zE%q3p{z@{qL9zdN_OUUzIlouGLEuqS-*?!j_wJuHV}N zO9;dIP#9tf(Sz(q1|>ThMPl0)fcNJSVh^4B&Z-5LJ%Srr+8|^wG35%tV_$mg2c#;< z9@IUKmQJ3UDw1G<<{HB1+EH^|UKcJw&y!)Jke$mFf~UXwQ*XBh5x;ta+`rAGgVqAn zq-Cr=%<60szi7@TwP&Wv0$qv0uMNpZ?YKNOh8&=?_xv%cdw2ye?)~&^E)ie)z5x?S z{gQ{-srHe7f8>XlXbA9QwmQRS@r$8^&K+AIa&gN*KPMIxHD=_F10ctkY!avu-}e}6 zja^lE+hJ7VWBMAtmuDS!MxdPU`t`DJz|>QFP)vIMPY}dGu?xoOmdn%eA%#St96M;A zdH&{|H}${UDD0oQEtdlVNI&;Eyu;qHbU=~0I zP`l?gWL?(@oOR8)(Mk!>IOuRU@@+5L$oJ-=g&bJ@km|Dnh(B=4uLP-1(Hf9zlhV!tAvVo`gb=F+{z9Ib>XGH9kT_BOy+R0y7l24I1RO`T zv13Z+x<-HHXI71g)pVllGbkrBB@DYRHm?an|8wBKgP<|EkX;?i5bVv{P0iL+dT^W8=W9rH;`I}6mS;7HWLg)FtQtT<>{usHX=kf@tOFKyEcYg#lcr#i z%K-r^fbxPO(#dgxn5AJD&zwK{qGK5;;GZb_b+aK+sbNvAxGY^4#d7~s=idEgK`G2_ z<&Z%2I!L&RhMuOa<*I^9EUV>W42us-5xlW!1!QR(twLVdBSeHW>sNPcBdgHc*Z;*`~DoZRXR~x8oOvY?^bs$V^k`Q?vc~jo{VR(vA zy%y4KQn$(bLe?7brS=Ng-lRrqHu|b#=Sv-KZdPiCqghDo&JeA>PNRe?JtX#_I*<&z z_PI9Q_}ru9t}*l|$;m1!0vqlsweM5x;i@wsje$}osKi*EbF9-M)oH_Qe}~~a?khWe z`7#KWJaRT$o2-QdVdLJ>CQPu$SfK^O^;WE|ab(q?mc+P*bL~u#uEPS}njw`yDFr!b zh7;=a_(z^>KEpkAm1M92XWblb}+?D*Ha2pXGf+>a2br3mF6oD)je z3a5jVa)=-Swm+oKb!ixXZM0D4(gB?=Rw+b_(kqmZ&cNw`R2H}lD5OQRz)C1`KU$z3 z3z}*Y%B_de4k-jp)FK2iH>u%dL5GMp6eoseBX&)mofgaYYz+8ppZVR~f(DHJWZg}v zzjlLVflB^-^(THd%7O`=V#SaSC6fa)MM3$r>!1%5rAB&GD`_M3us~zS4Hf{KN0Hp# zc=FucPodzrf#0fPBDyHog2=dqnnrTRQ7q+Rl#O9ttJ%8$TddY5#qw9R(F!OT21WC| zmj%Y7<%hCYG~Y4y7*@v@-C8vApI{3JP*-wO-PiYxW?eUIQ@#utaxCZE#$*fav+;J( zv3j1#sbzt7lE1>Hl%Vus3=$f?Vb+aa4^LE{J-=htB9oT| zRtLej zU|mIk*$Bl#DJ9YgEt{jMDOO2}{i)q>g0f#IfRUwl}2j+r=a7;QB9u&&=&Wk2pQ@T|L1qUKQ z_IQ0{eL}uo$r|T~VI@;~p5b-LnJ9H4>(!SR6jBy=V?lPH;9VMM{N^eELre=v#;lbl z$m}>XjD<}9c9aR_ac*k1&uz?_(7UO^U7@gP<_}42fL_qE01gfXn+P)33YE_kBJbnb z4OKxFANh8{b@?EAehN|8MzKe+pcWpr6mN+#LfFWJJ*4c7p^OWGYGj3IQ^jXdQ3$D3 zjAF5_lg9liV2fK&_#9`+usO>D+X<*Ad8-xhpo*M;3Z-H6(_v~M!|`Q91Pk7afz0S7 zh(%LQ;u_@l{TP*uX(<1D9#oy})IizOTosj0gM;KEvH6w-b^@;BdCB6%bd!MO#CU3> zqO2i#)nboPzS{F*4BJg-I0o^^k**bL38~0}%sVJ&OP*xvNdyulE2R|$wm@o|hP>(e zkli!8rcgSLh_b+L0R+@}7fWO`A6E9Y$U_7}m9Tl*ut+H3akO467IICsbb&EU-VI?h=FekW!*t@@d!$r7Cj|K6lB*NHKyW zLEawAuPp)jA5uZwmY+NU9Ij9F@GPh&N{QWnOHsRU^mB@Ql?C<}xIoF4SUxIQ_pC%A zMYuy!Y!%yt#A$Q|X?&iO3qO@Y3=vU6+B-QC+xI*Wk%DGpxHrn{JVDlFfvXCx6-*+W zjywkmP)*2{0E)N;=lH-OEh5Qx&FuH30a3(5`lkA=G@_J7&#djFqFHCb$x3lxBL#$%J1QIvB` zX@N)(FohX-V|brxpS|x}ULweeQagvKiLA*l&P%1;by;A6v50h@rKu7&C?h0BVXCl?4tJj$OFr!*b=*`!dZW z)asW;EmHq-j?+C~oG8%yCdJmCI&;t6g16IwP7hDS##*jaXt%D4p|+0DuH?>8O!C>d z95PlO?AF%2@W3A_V&!EFa)&-UV|9p>dU5UEP|K*E1i9OCxE0PS6JSEzUuBR4^G1Y_ z9x7qNoRI?&^Cec3^P?H4kfJ$`*A^&*y(_W}Z2J`{$)_2)9ER^fc0z zrrELX)f+$V9p;oA7aR@wS}DaUh-A%a*h)5v&>zDCiq4R;NxM!$;M7pXq|605-1(u3 zW&TP}efHu#pGc<)6im=;qQ%D|w(U6O0qa=EVYZ{iP*SE;fLgkr$uS9a>puDD7FPYD z=g;5uSlx0Z=jn@CCP0C$oO}H1$KR6U7Cy&&{fFE?x#cY+8CE$Kc9&y@`79Nr#J0*M zr@!)^J6iek5X3vQ5H844I*_6h>UfTBz*p}6=OG>_66W2?f0yrYj?loQnZSo3QSweQ zS(A^S0uu^Xq%Ra4D04yFVvSJ0bK&hj*(#u#^OX2214sp3GKMsW9LKIvxcpZ5S!qCB zYI{nAaQ&PoD!oiVEh<2Q9M>=_$9vRQoy!Ceml3D~ma#2G#p<%x;zEb)%%)3~JDXVM zB@s*ACmf7eQ`b>at)vV~|%slP$q#MW;@AX-iKvo)%9cxULBGf29=cxo&CIG9mFDMYkAgdD5&iFzds#Cw+tu<3|fWg6a;dntT7K2Jwq%sHa;7WUKC^L%@m0F)^imp$Z^ zZ{+!Ulq?7bDM|;(cVlg*$+bc7G69f3&qvWIBVgl2`RP?|&b8!6l@UNKb56v>wSbVH z3r(dVoOu1jn{yuJXB~G|PsYREQpva14kp*?xjEHGiacE^K>e%tK2jziS(ZegG6D)S zNwT8?S)mG2PkU~S!$s4B~`huVUNu_P7Cfel!@b3lh*YnW~h)R=~B#vSakc#tN7rVx%v53n(Y)@;jzsv%eM4wG6Zs0Poijjk=pl z^|;ajHqx-%`ueJfI8JXU6Chtc_hTZOB2oxM33Qa5mP4kt@op6UXmKLQ&)qQ$uESnC z6ppZDCX}$B053+&CZz$skISo9c01=n)H%uoWvf)6Yp&z+(0lC#J2FK$&9#^gr$i}ZqgFbhEZmGGu%Z#(r1cE! zK~8lrO9P_8dbu9L%w?kt=usxds2#(9nE*0mPx&IoG`LSw^vN~N%Up{JZGFu3tRQX= zp?cKLCLVfz)-}S%b7hqEAv>loRDbGBd2eoLa_g?TIZi%nH@Ujwbd12gNEXOBSBC1D zZEU)fsxB2+P{+AVd-XkgR8G5?$|BH(7cM?b2IzIf1h36)7Awc^dg^fJrmK}<(c#`8==8=R~n$GY|k4d za3y~75}1kb@wWBsg{Wl9aLx|PNz&@wL>bI=6w__beH6zw!~_?`xTmde)Eg-erGQnh z_hd`sqin=x$mVzQ;!Pig%odg$W&@D@rVQDjifXdZf9*OJh`DI!jiZtGi|D|@QYL^6vuD#4?2&VX0A?KpgqYVd7qFr^xH7Xx~Q4_TH({nsEmLOf$c(yxxM6TIhGYb5U#jURG8NeLYcog zEwfvMfoodafOB#T1#XE-HcgKW``()1jJ;qJA`98xrcrVnr6+!rs|6q!@xkKS)mWy9 zsT&qB3QshZy4FNPE+!B%m2<0r>T3ihlcVB;-xNgk55o91g(FF)K - - - - - - - - - - - diff --git a/assets/themes/natuurpunt/parkingbike.svg b/assets/themes/natuurpunt/parkingbike.svg deleted file mode 100644 index 418e829fac..0000000000 --- a/assets/themes/natuurpunt/parkingbike.svg +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/assets/themes/natuurpunt/parkingmotor.svg b/assets/themes/natuurpunt/parkingmotor.svg deleted file mode 100644 index 8262c23da8..0000000000 --- a/assets/themes/natuurpunt/parkingmotor.svg +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - diff --git a/assets/themes/natuurpunt/parkingwheels.svg b/assets/themes/natuurpunt/parkingwheels.svg deleted file mode 100644 index 0b9346c6b7..0000000000 --- a/assets/themes/natuurpunt/parkingwheels.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - diff --git a/assets/themes/natuurpunt/picnic_table.svg b/assets/themes/natuurpunt/picnic_table.svg deleted file mode 100644 index 3b890c0261..0000000000 --- a/assets/themes/natuurpunt/picnic_table.svg +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/assets/themes/natuurpunt/pushchair.svg b/assets/themes/natuurpunt/pushchair.svg deleted file mode 100644 index aad1d3df52..0000000000 --- a/assets/themes/natuurpunt/pushchair.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/assets/themes/natuurpunt/toilets.svg b/assets/themes/natuurpunt/toilets.svg deleted file mode 100644 index f4acb2b6cc..0000000000 --- a/assets/themes/natuurpunt/toilets.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - diff --git a/assets/themes/natuurpunt/trail.svg b/assets/themes/natuurpunt/trail.svg deleted file mode 100644 index e145a2758f..0000000000 --- a/assets/themes/natuurpunt/trail.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - diff --git a/assets/themes/natuurpunt/urinal.svg b/assets/themes/natuurpunt/urinal.svg deleted file mode 100644 index ae8347bc23..0000000000 --- a/assets/themes/natuurpunt/urinal.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/assets/themes/natuurpunt/walk_wheelchair.svg b/assets/themes/natuurpunt/walk_wheelchair.svg deleted file mode 100644 index b86a4f2edd..0000000000 --- a/assets/themes/natuurpunt/walk_wheelchair.svg +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/assets/themes/natuurpunt/wheelchair.svg b/assets/themes/natuurpunt/wheelchair.svg deleted file mode 100644 index 6dba61ace2..0000000000 --- a/assets/themes/natuurpunt/wheelchair.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - diff --git a/assets/themes/surveillance/custom_theme.css b/assets/themes/surveillance/custom_theme.css deleted file mode 100644 index 02e139eeeb..0000000000 --- a/assets/themes/surveillance/custom_theme.css +++ /dev/null @@ -1,11 +0,0 @@ -html { - --subtle-detail-color: #2c2 !important; - --subtle-detail-color-contrast: white !important; - --popup-border: #00ff00 !important; - --catch-detail-color: #00ff00 !important; - --catch-detail-color-contrast: black !important; - --alert-color: #eb00ff !important; - --background-color: black !important; - --foreground-color: white !important; - --shadow-color: #0f0 !important; -} From beb86919a8fc224d058841851511e4fc82368b07 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 8 May 2023 23:53:52 +0200 Subject: [PATCH 125/257] Mark old input elements as deprecated, remove some unused input elements --- UI/Input/Checkboxes.ts | 3 + UI/Input/DropDown.ts | 3 + UI/Input/FileSelectorButton.ts | 3 + UI/Input/FixedInputElement.ts | 3 + UI/Input/FloorLevelInputElement.ts | 94 ------------------------------ UI/Input/InputElement.ts | 6 ++ UI/Input/InputElementMap.ts | 3 + UI/Input/RadioButton.ts | 3 + UI/Input/Slider.ts | 3 + UI/Input/TextField.ts | 11 ++-- UI/Input/VariableInputElement.ts | 32 ---------- 11 files changed, 33 insertions(+), 131 deletions(-) delete mode 100644 UI/Input/FloorLevelInputElement.ts delete mode 100644 UI/Input/VariableInputElement.ts diff --git a/UI/Input/Checkboxes.ts b/UI/Input/Checkboxes.ts index 2c72fc4eea..c02f36d9ec 100644 --- a/UI/Input/Checkboxes.ts +++ b/UI/Input/Checkboxes.ts @@ -5,6 +5,9 @@ import BaseUIElement from "../BaseUIElement" import InputElementMap from "./InputElementMap" import Translations from "../i18n/Translations" +/** + * @deprecated + */ export class CheckBox extends InputElementMap { constructor(el: BaseUIElement | string, defaultValue?: boolean) { super( diff --git a/UI/Input/DropDown.ts b/UI/Input/DropDown.ts index e75467654a..96ac90d214 100644 --- a/UI/Input/DropDown.ts +++ b/UI/Input/DropDown.ts @@ -3,6 +3,9 @@ import Translations from "../i18n/Translations" import { UIEventSource } from "../../Logic/UIEventSource" import BaseUIElement from "../BaseUIElement" +/** + * @deprecated + */ export class DropDown extends InputElement { private static _nextDropdownId = 0 public IsSelected: UIEventSource = new UIEventSource(false) diff --git a/UI/Input/FileSelectorButton.ts b/UI/Input/FileSelectorButton.ts index 5248cf51c6..2a409a8869 100644 --- a/UI/Input/FileSelectorButton.ts +++ b/UI/Input/FileSelectorButton.ts @@ -2,6 +2,9 @@ import BaseUIElement from "../BaseUIElement" import { InputElement } from "./InputElement" import { UIEventSource } from "../../Logic/UIEventSource" +/** + * @deprecated + */ export default class FileSelectorButton extends InputElement { private static _nextid private readonly _value = new UIEventSource(undefined) diff --git a/UI/Input/FixedInputElement.ts b/UI/Input/FixedInputElement.ts index b8c272fae9..5e239e9aad 100644 --- a/UI/Input/FixedInputElement.ts +++ b/UI/Input/FixedInputElement.ts @@ -3,6 +3,9 @@ import { UIEventSource } from "../../Logic/UIEventSource" import Translations from "../i18n/Translations" import BaseUIElement from "../BaseUIElement" +/** + * @deprecated + */ export class FixedInputElement extends InputElement { private readonly value: UIEventSource private readonly _comparator: (t0: T, t1: T) => boolean diff --git a/UI/Input/FloorLevelInputElement.ts b/UI/Input/FloorLevelInputElement.ts deleted file mode 100644 index 6d88eb6d93..0000000000 --- a/UI/Input/FloorLevelInputElement.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { InputElement } from "./InputElement" -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import Combine from "../Base/Combine" -import Slider from "./Slider" -import { ClickableToggle } from "./Toggle" -import { FixedUiElement } from "../Base/FixedUiElement" -import { VariableUiElement } from "../Base/VariableUIElement" -import BaseUIElement from "../BaseUIElement" - -export default class FloorLevelInputElement - extends VariableUiElement - implements InputElement -{ - private readonly _value: UIEventSource - - constructor( - currentLevels: Store>, - options?: { - value?: UIEventSource - } - ) { - const value = options?.value ?? new UIEventSource("0") - super( - currentLevels.map((levels) => { - const allLevels = Object.keys(levels) - allLevels.sort((a, b) => { - const an = Number(a) - const bn = Number(b) - if (isNaN(an) || isNaN(bn)) { - return a < b ? -1 : 1 - } - return an - bn - }) - return FloorLevelInputElement.constructPicker(allLevels, value) - }) - ) - - this._value = value - } - - private static constructPicker(levels: string[], value: UIEventSource): BaseUIElement { - let slider = new Slider(0, levels.length - 1, { vertical: true }) - const toggleClass = - "flex border-2 border-blue-500 w-10 h-10 place-content-center items-center border-box" - slider - .SetClass("flex elevator w-10") - .SetStyle(`height: ${2.5 * levels.length}rem; background: #00000000`) - - const values = levels.map((data, i) => - new ClickableToggle( - new FixedUiElement(data).SetClass("font-bold active bg-subtle " + toggleClass), - new FixedUiElement(data).SetClass("normal-background " + toggleClass), - slider.GetValue().sync( - (sliderVal) => { - return sliderVal === i - }, - [], - (isSelected) => { - return isSelected ? i : slider.GetValue().data - } - ) - ) - .ToggleOnClick() - .SetClass("flex w-10 h-10") - ) - - values.reverse(/* This is a new list, no side-effects */) - const combine = new Combine([new Combine(values), slider]) - combine.SetClass("flex flex-row overflow-hidden") - - slider.GetValue().addCallbackD((i) => { - if (levels === undefined) { - return - } - if (levels[i] == undefined) { - return - } - value.setData(levels[i]) - }) - value.addCallbackAndRunD((level) => { - const i = levels.findIndex((l) => l === level) - slider.GetValue().setData(i) - }) - return combine - } - - GetValue(): UIEventSource { - return this._value - } - - IsValid(t: string): boolean { - return false - } -} diff --git a/UI/Input/InputElement.ts b/UI/Input/InputElement.ts index cbb3a1aa2c..f0e32283af 100644 --- a/UI/Input/InputElement.ts +++ b/UI/Input/InputElement.ts @@ -1,11 +1,17 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" import BaseUIElement from "../BaseUIElement" +/** + * @deprecated + */ export interface ReadonlyInputElement extends BaseUIElement { GetValue(): Store IsValid(t: T): boolean } +/** + * @deprecated + */ export abstract class InputElement extends BaseUIElement implements ReadonlyInputElement { abstract GetValue(): UIEventSource abstract IsValid(t: T): boolean diff --git a/UI/Input/InputElementMap.ts b/UI/Input/InputElementMap.ts index 5ca998baf2..4e49c38b51 100644 --- a/UI/Input/InputElementMap.ts +++ b/UI/Input/InputElementMap.ts @@ -1,6 +1,9 @@ import { InputElement } from "./InputElement" import { Store, UIEventSource } from "../../Logic/UIEventSource" +/** + * @deprecated + */ export default class InputElementMap extends InputElement { private readonly _inputElement: InputElement private isSame: (x0: X, x1: X) => boolean diff --git a/UI/Input/RadioButton.ts b/UI/Input/RadioButton.ts index 7c4c2d3c40..15e1a425a6 100644 --- a/UI/Input/RadioButton.ts +++ b/UI/Input/RadioButton.ts @@ -2,6 +2,9 @@ import { InputElement } from "./InputElement" import { UIEventSource } from "../../Logic/UIEventSource" import { Utils } from "../../Utils" +/** + * @deprecated + */ export class RadioButton extends InputElement { private static _nextId = 0 diff --git a/UI/Input/Slider.ts b/UI/Input/Slider.ts index 272a0fa411..9fce626a7b 100644 --- a/UI/Input/Slider.ts +++ b/UI/Input/Slider.ts @@ -1,6 +1,9 @@ import { InputElement } from "./InputElement" import { UIEventSource } from "../../Logic/UIEventSource" +/** + * @deprecated + */ export default class Slider extends InputElement { private readonly _value: UIEventSource private readonly min: number diff --git a/UI/Input/TextField.ts b/UI/Input/TextField.ts index 9c0e2bc226..0e0ccbbb9f 100644 --- a/UI/Input/TextField.ts +++ b/UI/Input/TextField.ts @@ -4,6 +4,9 @@ import BaseUIElement from "../BaseUIElement" import { Translation } from "../i18n/Translation" import Locale from "../i18n/Locale" +/** + * @deprecated + */ interface TextFieldOptions { placeholder?: string | Store | Translation value?: UIEventSource @@ -15,6 +18,9 @@ interface TextFieldOptions { isValid?: (s: string) => boolean } +/** + * @deprecated + */ export class TextField extends InputElement { public readonly enterPressed = new UIEventSource(undefined) private readonly value: UIEventSource @@ -124,11 +130,6 @@ export class TextField extends InputElement { this.value.addCallbackAndRunD((value) => { // We leave the textfield as is in the case of undefined or null (handled by addCallbackAndRunD) - make sure we do not erase it! field["value"] = value - if (self.IsValid(value)) { - self.RemoveClass("invalid") - } else { - self.SetClass("invalid") - } }) field.oninput = () => { diff --git a/UI/Input/VariableInputElement.ts b/UI/Input/VariableInputElement.ts deleted file mode 100644 index 0fdae6c784..0000000000 --- a/UI/Input/VariableInputElement.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ReadonlyInputElement } from "./InputElement" -import { Store } from "../../Logic/UIEventSource" -import BaseUIElement from "../BaseUIElement" -import { VariableUiElement } from "../Base/VariableUIElement" - -export default class VariableInputElement - extends BaseUIElement - implements ReadonlyInputElement -{ - private readonly value: Store - private readonly element: BaseUIElement - private readonly upstream: Store> - - constructor(upstream: Store>) { - super() - this.upstream = upstream - this.value = upstream.bind((v) => v.GetValue()) - this.element = new VariableUiElement(upstream) - } - - GetValue(): Store { - return this.value - } - - IsValid(t: T): boolean { - return this.upstream.data.IsValid(t) - } - - protected InnerConstructElement(): HTMLElement { - return this.element.ConstructElement() - } -} From ab28fbe35c97de56e765bc3b35277489aebb1872 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 9 May 2023 00:05:38 +0200 Subject: [PATCH 126/257] Fix: fix icons in mappings which use a builtin svg --- Models/ThemeConfig/PointRenderingConfig.ts | 19 +++++---- Models/ThemeConfig/TagRenderingConfig.ts | 45 ++++++++++++---------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts index d32926be2d..c3cc1059ac 100644 --- a/Models/ThemeConfig/PointRenderingConfig.ts +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -1,17 +1,16 @@ import PointRenderingConfigJson from "./Json/PointRenderingConfigJson" import TagRenderingConfig from "./TagRenderingConfig" -import { TagsFilter } from "../../Logic/Tags/TagsFilter" -import { TagUtils } from "../../Logic/Tags/TagUtils" -import { Utils } from "../../Utils" +import {TagsFilter} from "../../Logic/Tags/TagsFilter" +import {TagUtils} from "../../Logic/Tags/TagUtils" +import {Utils} from "../../Utils" import Svg from "../../Svg" import WithContextLoader from "./WithContextLoader" -import { Store } from "../../Logic/UIEventSource" +import {Store} from "../../Logic/UIEventSource" import BaseUIElement from "../../UI/BaseUIElement" -import { FixedUiElement } from "../../UI/Base/FixedUiElement" +import {FixedUiElement} from "../../UI/Base/FixedUiElement" import Img from "../../UI/Base/Img" import Combine from "../../UI/Base/Combine" -import { VariableUiElement } from "../../UI/Base/VariableUIElement" -import Constants from "../Constants"; +import {VariableUiElement} from "../../UI/Base/VariableUIElement" export default class PointRenderingConfig extends WithContextLoader { static readonly allowed_location_codes: ReadonlySet = new Set([ @@ -84,7 +83,7 @@ export default class PointRenderingConfig extends WithContextLoader { } }) - const iconPath = this.icon?.GetRenderValue({ id: "node/-1" })?.txt + const iconPath = this.icon?.GetRenderValue({id: "node/-1"})?.txt if (iconPath !== undefined && iconPath.startsWith(Utils.assets_path)) { const iconKey = iconPath.substr(Utils.assets_path.length) if (Svg.All[iconKey] === undefined) { @@ -110,7 +109,7 @@ export default class PointRenderingConfig extends WithContextLoader { return undefined } const match = htmlSpec.match(/([a-zA-Z0-9_]*):([^;]*)/) - if (match !== null && Constants.defaultPinIcons.indexOf(match[1] ) >= 0) { + if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) { const svg = Svg.All[match[1] + ".svg"] as string const targetColor = match[2] const img = new Img( @@ -169,7 +168,7 @@ export default class PointRenderingConfig extends WithContextLoader { noFullWidth?: boolean } ): BaseUIElement { - tags = tags ?? { id: "node/-1" } + tags = tags ?? {id: "node/-1"} let defaultPin: BaseUIElement = undefined if (this.label === undefined) { defaultPin = Svg.teardrop_with_hole_green_svg() diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index 6dc60840e8..b0890ab5a3 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -15,7 +15,6 @@ import {FixedUiElement} from "../../UI/Base/FixedUiElement" import {Paragraph} from "../../UI/Base/Paragraph" import Svg from "../../Svg" import Validators, {ValidatorType} from "../../UI/InputElement/Validators"; -import Constants from "../Constants"; export interface Mapping { readonly if: UploadableTag @@ -69,6 +68,7 @@ export default class TagRenderingConfig { public readonly mappings?: Mapping[] public readonly labels: string[] public readonly classes: string[] + constructor(json: string | QuestionableTagRenderingConfigJson, context?: string) { if (json === undefined) { throw "Initing a TagRenderingConfig with undefined in " + context @@ -118,9 +118,9 @@ export default class TagRenderingConfig { this.question = Translations.T(json.question, translationKey + ".question") this.questionhint = Translations.T(json.questionHint, translationKey + ".questionHint") this.description = Translations.T(json.description, translationKey + ".description") - this.condition = TagUtils.Tag(json.condition ?? { and: [] }, `${context}.condition`) + this.condition = TagUtils.Tag(json.condition ?? {and: []}, `${context}.condition`) this.metacondition = TagUtils.Tag( - json.metacondition ?? { and: [] }, + json.metacondition ?? {and: []}, `${context}.metacondition` ) if (json.freeform) { @@ -379,9 +379,7 @@ export default class TagRenderingConfig { if (stripped.endsWith(".svg")) { stripped = stripped.substring(0, stripped.length - 4) } - if ( - Constants.defaultPinIcons.indexOf(stripped) - ) { + if (Svg.All[stripped + ".svg"] !== undefined) { icon = "./assets/svg/" + mapping.icon if (!icon.endsWith(".svg")) { icon += ".svg" @@ -544,7 +542,7 @@ export default class TagRenderingConfig { this.freeform?.key === undefined || tags[this.freeform.key] !== undefined ) { - return { then: this.render } + return {then: this.render} } return undefined @@ -634,9 +632,9 @@ export default class TagRenderingConfig { currentProperties: Record ): UploadableTag { freeformValue = freeformValue?.trim() - const validator = Validators.get( this.freeform?.type) - if(validator && freeformValue){ - freeformValue = validator.reformat(freeformValue,() => currentProperties["_country"]) + const validator = Validators.get(this.freeform?.type) + if (validator && freeformValue) { + freeformValue = validator.reformat(freeformValue, () => currentProperties["_country"]) } if (freeformValue === "") { freeformValue = undefined @@ -687,12 +685,12 @@ export default class TagRenderingConfig { } else { // Is at least one mapping shown in the answer? const someMappingIsShown = this.mappings.some(m => { - if(typeof m.hideInAnswer === "boolean"){ + if (typeof m.hideInAnswer === "boolean") { return !m.hideInAnswer } const isHidden = m.hideInAnswer.matchesProperties(currentProperties) return !isHidden - } ) + }) // If all mappings are hidden for the current tags, we can safely assume that we should use the freeform key const useFreeform = freeformValue !== undefined && (singleSelectedMapping === this.mappings.length || !someMappingIsShown) if (useFreeform) { @@ -700,13 +698,18 @@ export default class TagRenderingConfig { new Tag(this.freeform.key, freeformValue), ...(this.freeform.addExtraTags ?? []), ]) - } else if(singleSelectedMapping !== undefined) { + } else if (singleSelectedMapping !== undefined) { return new And([ this.mappings[singleSelectedMapping].if, ...(this.mappings[singleSelectedMapping].addExtraTags ?? []), ]) - }else{ - console.warn("TagRenderingConfig.ConstructSpecification has a weird fallback for", {freeformValue, singleSelectedMapping, multiSelectedMapping, currentProperties}) + } else { + console.warn("TagRenderingConfig.ConstructSpecification has a weird fallback for", { + freeformValue, + singleSelectedMapping, + multiSelectedMapping, + currentProperties + }) return undefined } } @@ -751,7 +754,7 @@ export default class TagRenderingConfig { if (m.ifnot !== undefined) { msgs.push( "Unselecting this answer will add " + - m.ifnot.asHumanString(true, false, {}) + m.ifnot.asHumanString(true, false, {}) ) } return msgs @@ -781,12 +784,12 @@ export default class TagRenderingConfig { this.description, this.question !== undefined ? new Combine([ - "The question is ", - new FixedUiElement(this.question.txt).SetClass("font-bold bold"), - ]) + "The question is ", + new FixedUiElement(this.question.txt).SetClass("font-bold bold"), + ]) : new FixedUiElement( - "This tagrendering has no question and is thus read-only" - ).SetClass("italic"), + "This tagrendering has no question and is thus read-only" + ).SetClass("italic"), new Combine(withRender), mappings, condition, From 94111dbdb7133412b85860ab104fc6ded957f626 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 9 May 2023 00:06:51 +0200 Subject: [PATCH 127/257] Refactoring: removal of many obsolete css classes --- StylesheetTestGui.ts | 4 + UI/Popup/DeleteWizard.ts | 4 +- UI/Popup/LanguageElement.ts | 2 +- UI/Popup/SaveButton.ts | 19 +- UI/Reviews/ReviewForm.ts | 4 +- UI/Reviews/SingleReview.ts | 12 +- UI/SpecialVisualizations.ts | 2 +- UI/StylesheetTestGui.svelte | 58 ++++++ .../bike_cleaning/bike_cleaning_icon.svg | 83 ++++++-- .../charging_station/charging_station.json | 2 +- assets/svg/license_info.json | 58 ------ css/index-tailwind-output.css | 194 +++--------------- css/mobile.css | 69 +------ css/tagrendering.css | 84 ++++---- index.css | 182 ++-------------- 15 files changed, 253 insertions(+), 524 deletions(-) create mode 100644 StylesheetTestGui.ts create mode 100644 UI/StylesheetTestGui.svelte diff --git a/StylesheetTestGui.ts b/StylesheetTestGui.ts new file mode 100644 index 0000000000..2ebe1dfbbe --- /dev/null +++ b/StylesheetTestGui.ts @@ -0,0 +1,4 @@ +import SvelteUIElement from "./UI/Base/SvelteUIElement"; +import StylesheetTestGui from "./UI/StylesheetTestGui.svelte"; + +new SvelteUIElement(StylesheetTestGui, {}).AttachTo("main") diff --git a/UI/Popup/DeleteWizard.ts b/UI/Popup/DeleteWizard.ts index 18cb6241db..637fb32672 100644 --- a/UI/Popup/DeleteWizard.ts +++ b/UI/Popup/DeleteWizard.ts @@ -108,9 +108,7 @@ export default class DeleteWizard extends Toggle { const deleteOptionPicker = DeleteWizard.constructMultipleChoice(options, tagsSource, state) const deleteDialog = new Combine([ new Title( - new SubstitutedTranslation(t.whyDelete, tagsSource, state).SetClass( - "question-text" - ), + new SubstitutedTranslation(t.whyDelete, tagsSource, state), 3 ), deleteOptionPicker, diff --git a/UI/Popup/LanguageElement.ts b/UI/Popup/LanguageElement.ts index b07e90232f..7540262289 100644 --- a/UI/Popup/LanguageElement.ts +++ b/UI/Popup/LanguageElement.ts @@ -143,7 +143,7 @@ export class LanguageElement implements SpecialVisualization { const saveButton = new SaveButton( selector.GetValue().map((lngs) => (lngs.length > 0 ? "true" : undefined)), - state.osmConnection + state ).onClick(() => { const selectedLanguages = selector.GetValue().data const currentLanguages = foundLanguages.data diff --git a/UI/Popup/SaveButton.ts b/UI/Popup/SaveButton.ts index 63968ece5d..fba21d610e 100644 --- a/UI/Popup/SaveButton.ts +++ b/UI/Popup/SaveButton.ts @@ -1,10 +1,11 @@ -import { ImmutableStore, Store } from "../../Logic/UIEventSource" +import {Store} from "../../Logic/UIEventSource" import Translations from "../i18n/Translations" -import { OsmConnection } from "../../Logic/Osm/OsmConnection" +import {OsmConnection} from "../../Logic/Osm/OsmConnection" import Toggle from "../Input/Toggle" import BaseUIElement from "../BaseUIElement" import Combine from "../Base/Combine" import Svg from "../../Svg" +import {LoginToggle} from "./LoginButton"; export class EditButton extends Toggle { constructor(osmConnection: OsmConnection, onClick: () => void) { @@ -19,10 +20,13 @@ export class EditButton extends Toggle { } } -export class SaveButton extends Toggle { +export class SaveButton extends LoginToggle { constructor( value: Store, - osmConnection: OsmConnection, + state: { + readonly osmConnection?: OsmConnection + readonly featureSwitchUserbadge?: Store + }, textEnabled?: BaseUIElement, textDisabled?: BaseUIElement ) { @@ -30,11 +34,6 @@ export class SaveButton extends Toggle { throw "No event source for savebutton, something is wrong" } - const pleaseLogin = Translations.t.general.loginToStart - .Clone() - .SetClass("login-button-friendly") - .onClick(() => osmConnection?.AttemptLogin()) - const isSaveable = value.map((v) => v !== false && (v ?? "") !== "") const saveEnabled = (textEnabled ?? Translations.t.general.save.Clone()).SetClass(`btn`) @@ -43,6 +42,6 @@ export class SaveButton extends Toggle { ) const save = new Toggle(saveEnabled, saveDisabled, isSaveable) - super(save, pleaseLogin, osmConnection?.isLoggedIn ?? new ImmutableStore(false)) + super(save, Translations.t.general.loginToStart, state) } } diff --git a/UI/Reviews/ReviewForm.ts b/UI/Reviews/ReviewForm.ts index dd9928f91a..dd604f6db4 100644 --- a/UI/Reviews/ReviewForm.ts +++ b/UI/Reviews/ReviewForm.ts @@ -44,9 +44,7 @@ export default class ReviewForm extends LoginToggle { const saveButton = new Toggle( Translations.t.reviews.no_rating.SetClass("block alert"), - new SubtleButton(Svg.confirm_svg(), Translations.t.reviews.save, { - extraClasses: "border-attention-catch", - }) + new SubtleButton(Svg.confirm_svg(), Translations.t.reviews.save) .OnClickWithLoading( Translations.t.reviews.saving_review.SetClass("alert"), async () => { diff --git a/UI/Reviews/SingleReview.ts b/UI/Reviews/SingleReview.ts index ee18ca3d00..de51a226a5 100644 --- a/UI/Reviews/SingleReview.ts +++ b/UI/Reviews/SingleReview.ts @@ -13,13 +13,15 @@ export default class SingleReview extends Combine { const reviewAuthor = review.metadata.nickname ?? (review.metadata.given_name ?? "") + (review.metadata.family_name ?? "") + const authorElement = new FixedUiElement(reviewAuthor).SetClass("font-bold") + super([ new Combine([SingleReview.GenStars(review.rating)]), new FixedUiElement(review.opinion), new Combine([ new Combine([ - new FixedUiElement(reviewAuthor).SetClass("font-bold"), - review.metadata.is_affiliated + authorElement, + review.metadata.is_affiliated ? Translations.t.reviews.affiliated_reviewer_warning : "", ]).SetStyle("margin-right: 0.5em"), @@ -29,15 +31,15 @@ export default class SingleReview extends Combine { )}-${Utils.TwoDigits(date.getDate())} ${Utils.TwoDigits( date.getHours() )}:${Utils.TwoDigits(date.getMinutes())}` - ).SetClass("subtle-lighter"), + ).SetClass("subtle"), ]).SetClass("flex mb-4 justify-end"), ]) this.SetClass("block p-2 m-4 rounded-xl subtle-background review-element") review.madeByLoggedInUser.addCallbackAndRun((madeByUser) => { if (madeByUser) { - this.SetClass("border-attention-catch") + authorElement.SetClass("thanks") } else { - this.RemoveClass("border-attention-catch") + authorElement.RemoveClass("thanks") } }) } diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index c3365b1018..0037dd0308 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -129,7 +129,7 @@ class NearbyImageVis implements SpecialVisualization { } saveButton = new SaveButton( selectedImage, - state.osmConnection, + state, confirmText, t.noImageSelected ) diff --git a/UI/StylesheetTestGui.svelte b/UI/StylesheetTestGui.svelte new file mode 100644 index 0000000000..635579ce32 --- /dev/null +++ b/UI/StylesheetTestGui.svelte @@ -0,0 +1,58 @@ + + + diff --git a/assets/layers/bike_cleaning/bike_cleaning_icon.svg b/assets/layers/bike_cleaning/bike_cleaning_icon.svg index 7283c43589..92a6739fef 100644 --- a/assets/layers/bike_cleaning/bike_cleaning_icon.svg +++ b/assets/layers/bike_cleaning/bike_cleaning_icon.svg @@ -1,18 +1,67 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 905914b381..7722b44973 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -5218,4 +5218,4 @@ }, "neededChangesets": 10 } -} +} \ No newline at end of file diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index eadb7dba08..fc572a1239 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -59,18 +59,6 @@ ], "sources": [] }, - { - "path": "ampersand.svg", - "license": "CC0; trivial", - "authors": [], - "sources": [] - }, - { - "path": "ampersand.svg", - "license": "CC0; trivial", - "authors": [], - "sources": [] - }, { "path": "back.svg", "license": "CC0", @@ -165,22 +153,6 @@ "https://commons.wikimedia.org/wiki/File:Camera_font_awesome.svg" ] }, - { - "path": "checkbox-empty.svg", - "license": "CC0", - "authors": [ - "Hannah Declerck" - ], - "sources": [] - }, - { - "path": "checkbox-filled.svg", - "license": "CC0", - "authors": [ - "Hannah Declerck" - ], - "sources": [] - }, { "path": "checkmark.svg", "license": "CC0", @@ -289,36 +261,6 @@ "authors": [], "sources": [] }, - { - "path": "crosshair-blue-center.svg", - "license": "CC0; trivial", - "authors": [], - "sources": [] - }, - { - "path": "crosshair-blue-center.svg", - "license": "CC0; trivial", - "authors": [], - "sources": [] - }, - { - "path": "crosshair-blue.svg", - "license": "CC0; trivial", - "authors": [], - "sources": [] - }, - { - "path": "crosshair-blue.svg", - "license": "CC0; trivial", - "authors": [], - "sources": [] - }, - { - "path": "crosshair-empty.svg", - "license": "CC0; trivial", - "authors": [], - "sources": [] - }, { "path": "crosshair-locked.svg", "license": "CC0; trivial", diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index eb15473318..02043b7d3a 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -763,14 +763,6 @@ video { margin: 1.25rem; } -.m-0\.5 { - margin: 0.125rem; -} - -.m-0 { - margin: 0px; -} - .m-3 { margin: 0.75rem; } @@ -783,6 +775,14 @@ video { margin: 0.25rem; } +.m-0\.5 { + margin: 0.125rem; +} + +.m-0 { + margin: 0px; +} + .m-4 { margin: 1rem; } @@ -989,10 +989,6 @@ video { height: 8rem; } -.h-10 { - height: 2.5rem; -} - .h-12 { height: 3rem; } @@ -1039,6 +1035,10 @@ video { height: 10rem; } +.h-10 { + height: 2.5rem; +} + .h-80 { height: 20rem; } @@ -1075,10 +1075,6 @@ video { width: 8rem; } -.w-10 { - width: 2.5rem; -} - .w-12 { width: 3rem; } @@ -1130,6 +1126,10 @@ video { width: 6rem; } +.w-10 { + width: 2.5rem; +} + .w-48 { width: 12rem; } @@ -1241,10 +1241,6 @@ video { flex-wrap: wrap-reverse; } -.place-content-center { - place-content: center; -} - .content-start { align-content: flex-start; } @@ -1428,11 +1424,6 @@ video { border-color: rgb(132 204 22 / var(--tw-border-opacity)); } -.border-blue-500 { - --tw-border-opacity: 1; - border-color: rgb(59 130 246 / var(--tw-border-opacity)); -} - .border-opacity-50 { --tw-border-opacity: 0.5; } @@ -1805,15 +1796,6 @@ video { transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } -.z-above-map { - z-index: 10000; -} - -.bg-subtle { - background-color: var(--subtle-detail-color); - color: var(--subtle-detail-color-contrast); -} - .\[key\:string\] { key: string; } @@ -1823,19 +1805,20 @@ video { } :root { - /* The main colour scheme of mapcomplete is configured here. - * For a custom styling, set 'customCss' in your layoutConfig and overwrite some of these. - */ + /* + * The main colour scheme of mapcomplete is configured here. + * For a custom styling, set 'customCss' in your layoutConfig and overwrite some of these. + */ /* Main color of the application: the background and text colours */ --background-color: white; /* Main text colour. Also styles some elements, such as the 'close popup'-button or 'back-arrow' (in mobile) */ --foreground-color: black; - /* A colour to indicate an error or warning */ + /* A colour scheme to indicate an error or warning */ --alert-color: #fee4d1; + --alert-foreground-color: var(--foreground-color); /** - * Base colour of interactive elements, mainly the 'subtle button' - * - */ + * Base colour of interactive elements, mainly the 'subtle button' + */ --subtle-detail-color: #dbeafe; --subtle-detail-color-contrast: black; --subtle-detail-color-light-contrast: lightgrey; @@ -1847,11 +1830,7 @@ video { --unsubtle-detail-color-contrast: black; --catch-detail-color: #3a3aeb; --catch-detail-color-contrast: white; - --non-active-tab-svg: var(--foreground-color); - --shadow-color: #00000066; --image-carousel-height: 350px; - /* Technical variable to make some dynamic behaviour possible; set by javascript. */ - --variable-title-height: 0px; } html, @@ -1936,34 +1915,6 @@ a { border: 3px solid var(--unsubtle-detail-color); } -/* slider */ - -input[type="range"].vertical { - -webkit-writing-mode: bt-lr; - writing-mode: bt-lr; - /* IE */ - -webkit-appearance: slider-vertical; - /* Chromium */ - cursor: pointer; -} - -@-moz-document url-prefix() { - input[type="range"].elevator::-moz-range-thumb { - background-color: #00000000 !important; - background-image: url("/assets/svg/elevator_wheelchair.svg"); - width: 150px !important; - height: 30px !important; - border: 2px; - border-style: solid; - background-size: contain; - background-position: center center; - background-repeat: no-repeat; - cursor: pointer; - border-image: linear-gradient(to right, black 50%, transparent 50%) 100% 1; - padding-bottom: 5px; - } -} - .rounded-left-full { border-bottom-left-radius: 999rem; border-top-left-radius: 999rem; @@ -1995,6 +1946,8 @@ li { } h2 { + font-size: x-large; + margin-bottom: 0. 4em; font-size: large; margin-top: 0.5em; margin-bottom: 0.3em; @@ -2027,22 +1980,6 @@ li::marker { color: var(--foreground-color); } -.subtle-lighter { - color: var(--subtle-detail-color-light-contrast); -} - -.border-attention-catch { - border: 5px solid var(--catch-detail-color); -} - -.border-attention { - border-color: var(--catch-detail-color); -} - -.direction-svg svg path { - fill: var(--catch-detail-color) !important; -} - .block-ruby { display: block ruby; } @@ -2066,6 +2003,8 @@ li::marker { } .selected svg:not(.noselect *) path.selectable { + /* A marker on the map gets the 'selected' class when it's properties are displayed + */ stroke: white !important; stroke-width: 20px !important; overflow: visible !important; @@ -2074,6 +2013,8 @@ li::marker { } .selected svg { + /* A marker on the map gets the 'selected' class when it's properties are displayed + */ overflow: visible !important; } @@ -2093,7 +2034,7 @@ li::marker { .alert { background-color: var(--alert-color); - color: var(--foreground-color); + color: var(--alert-foreground-color); font-weight: bold; border-radius: 1em; margin: 0.25em; @@ -2101,31 +2042,10 @@ li::marker { padding: 0.15em 0.3em; } -.invalid { - box-shadow: 0 0 10px #ff5353; - height: -webkit-min-content; - height: min-content; -} - .shadow { box-shadow: 0 0 20px var(--shadow-color); } -.title-font span { - font-size: xx-large !important; - font-weight: bold; -} - -.soft { - background-color: var(--subtle-detail-color); - color: var(--subtle-detail-color-contrast); - font-weight: bold; - border-radius: 1em; - margin: 0.25em; - text-align: center; - padding: 0.15em 0.3em; -} - .subtle { color: #999; } @@ -2229,52 +2149,6 @@ input { } } -.mapping-icon-small-height { - /* A mapping icon type */ - height: 1.5rem; - margin-right: 0.5rem; - width: unset; -} - -.mapping-icon-medium-height { - /* A mapping icon type */ - height: 3rem; - margin-right: 0.5rem; - width: unset; -} - -.mapping-icon-large-height { - /* A mapping icon type */ - height: 5rem; - margin-right: 0.5rem; - width: unset; -} - -.mapping-icon-small { - /* A mapping icon type */ - width: 1.5rem; - max-height: 1.5rem; - margin-right: 0.5rem; -} - -.mapping-icon-medium { - /* A mapping icon type */ - width: 3rem; - max-height: 3rem; - margin-right: 1rem; - margin-left: 1rem; -} - -.mapping-icon-large { - /* A mapping icon type */ - width: 6rem; - max-height: 5rem; - margin-top: 0.5rem; - margin-bottom: 0.5rem; - margin-right: 1.5rem; - margin-left: 1.5rem; -} - .hover\:bg-unsubtle:hover { --tw-bg-opacity: 1; background-color: rgb(191 219 254 / var(--tw-bg-opacity)); @@ -2296,11 +2170,6 @@ input { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -.hover\:bg-unsubtle:hover { - background-color: var(--unsubtle-detail-color); - color: var(--unsubtle-detail-color-contrast); -} - @media (max-width: 480px) { .max-\[480px\]\:w-full { width: 100%; @@ -2530,3 +2399,4 @@ input { width: 33.333333%; } } + diff --git a/css/mobile.css b/css/mobile.css index dfef43f644..c574cdf882 100644 --- a/css/mobile.css +++ b/css/mobile.css @@ -1,77 +1,10 @@ -/* -Contains tweaks for small screens -*/ - -@media only screen and (min-width: 769px) and (min-height: 700px) { - - .only-on-mobile { - display: none !important; - } - -} - - -@media only screen and (min-height: 175px) and (min-width: 175px) { - .very-small-screen { - display: none !important; - } - - - .hidden-on-very-small-screen { - } - -} - - -@media not screen and (min-height: 175px) and (min-width: 175px) { - .very-small-screen { - } - - .hidden-on-very-small-screen { - display: none !important; - } - -} - @media only screen and (max-width: 768px), only screen and (max-height: 700px) { - - .hidden-on-mobile { display: none !important; } - - #centermessage { - top: 30%; - left: 15%; - width: 60%; - - } - - .add-popup-all-buttons { - /* Buttons in the 'add new point' have a default of 50vh maxheight which is ugly in the new context*/ - max-height: unset !important; - } - - #messagesboxmobile { - display: block; - - position: absolute; - z-index: 10000; - width: 100vw; - height: 100vh; - } -} - -@media only screen and (max-width: 768px) { - #leafletDiv .leaflet-control-attribution { - display: none; - } - - .leaflet-popup { - display: none; - } } + diff --git a/css/tagrendering.css b/css/tagrendering.css index 09634d2e9d..4c63a909b2 100644 --- a/css/tagrendering.css +++ b/css/tagrendering.css @@ -1,4 +1,8 @@ +/** + Some utility functions which are only used to render data + */ + .question .form-text-field > input { width: 100%; box-sizing: border-box; @@ -26,43 +30,53 @@ height: 100%; } -.question-text { - font-size: larger; - font-weight: bold; - margin-bottom: 0.5em; + + + +.mapping-icon-small-height { + /* A mapping icon type */ + height: 1.5rem; + margin-right: 0.5rem; + width: unset; } -.question-text img { - max-width: 100%; -} - - - -.question-option-with-border { - border: 2px solid lightgray; - border-radius: 0.5em; - display: block; - width: 100%; - margin: 5px; - box-sizing: border-box; - padding: 0.5em; -} - -input:checked + label .question-option-with-border { - border: 2px solid var(--subtle-detail-color-contrast); -} - -.login-button-friendly { - display: inline-block; - background-color: var(--catch-detail-color); - color: var(--catch-detail-color-contrast); - border: solid var(--catch-detail-color-contrast) 2px; - padding: 0.2em 0.6em; - font-size: large; - font-weight: bold; - border-radius: 1.5em; - box-sizing: border-box; - width: 100%; +.mapping-icon-medium-height { + /* A mapping icon type */ + height: 3rem; + margin-right: 0.5rem; + width: unset; +} + +.mapping-icon-large-height { + /* A mapping icon type */ + height: 5rem; + margin-right: 0.5rem; + width: unset; +} + +.mapping-icon-small { + /* A mapping icon type */ + width: 1.5rem; + max-height: 1.5rem; + margin-right: 0.5rem; +} + +.mapping-icon-medium { + /* A mapping icon type */ + width: 3rem; + max-height: 3rem; + margin-right: 1rem; + margin-left: 1rem; +} + +.mapping-icon-large { + /* A mapping icon type */ + width: 6rem; + max-height: 5rem; + margin-top: 0.5rem; + margin-bottom: 0.5rem; + margin-right: 1.5rem; + margin-left: 1.5rem; } diff --git a/index.css b/index.css index f3fe601b07..1ea500cc07 100644 --- a/index.css +++ b/index.css @@ -11,62 +11,24 @@ @tailwind components; @tailwind utilities; -@layer utilities { - .z-above-map { - z-index: 10000; - } - - .z-above-controls { - z-index: 10001; - } - - .w-160 { - width: 40rem; - } - - .bg-subtle { - background-color: var(--subtle-detail-color); - color: var(--subtle-detail-color-contrast); - } - - .bg-unsubtle { - background-color: var(--unsubtle-detail-color); - color: var(--unsubtle-detail-color-contrast); - } - - .bg-catch { - background-color: var(--catch-detail-color); - color: var(--catch-detail-color-contrast); - } - - .rounded-left-full { - border-bottom-left-radius: 999rem; - border-top-left-radius: 999rem; - } - - .rounded-right-full { - border-bottom-right-radius: 999rem; - border-top-right-radius: 999rem; - } -} - :root { - /* The main colour scheme of mapcomplete is configured here. - * For a custom styling, set 'customCss' in your layoutConfig and overwrite some of these. - */ + /* + * The main colour scheme of mapcomplete is configured here. + * For a custom styling, set 'customCss' in your layoutConfig and overwrite some of these. + */ /* Main color of the application: the background and text colours */ --background-color: white; /* Main text colour. Also styles some elements, such as the 'close popup'-button or 'back-arrow' (in mobile) */ --foreground-color: black; - /* A colour to indicate an error or warning */ + /* A colour scheme to indicate an error or warning */ --alert-color: #fee4d1; + --alert-foreground-color: var(--foreground-color); /** - * Base colour of interactive elements, mainly the 'subtle button' - * - */ + * Base colour of interactive elements, mainly the 'subtle button' + */ --subtle-detail-color: #dbeafe; --subtle-detail-color-contrast: black; --subtle-detail-color-light-contrast: lightgrey; @@ -81,13 +43,9 @@ --catch-detail-color: #3a3aeb; --catch-detail-color-contrast: white; - --non-active-tab-svg: var(--foreground-color); - --shadow-color: #00000066; --image-carousel-height: 350px; - /* Technical variable to make some dynamic behaviour possible; set by javascript. */ - --variable-title-height: 0px; } html, @@ -173,30 +131,6 @@ a { border: 3px solid var(--unsubtle-detail-color); } -/* slider */ -input[type="range"].vertical { - writing-mode: bt-lr; /* IE */ - -webkit-appearance: slider-vertical; /* Chromium */ - cursor: pointer; -} - -@-moz-document url-prefix() { - input[type="range"].elevator::-moz-range-thumb { - background-color: #00000000 !important; - background-image: url("/assets/svg/elevator_wheelchair.svg"); - width: 150px !important; - height: 30px !important; - border: 2px; - border-style: solid; - background-size: contain; - background-position: center center; - background-repeat: no-repeat; - cursor: pointer; - border-image: linear-gradient(to right, black 50%, transparent 50%) 100% 1; - padding-bottom: 5px; - } -} - .rounded-left-full { border-bottom-left-radius: 999rem; border-top-left-radius: 999rem; @@ -225,6 +159,14 @@ li { margin-top: 0.1em; } +h1 { + font-size: x-large; + margin-top: 0.6em; + margin-bottom: 0.4em; + font-weight: bold; +} + + h2 { font-size: large; margin-top: 0.5em; @@ -264,22 +206,6 @@ li::marker { color: var(--foreground-color); } -.subtle-lighter { - color: var(--subtle-detail-color-light-contrast); -} - -.border-attention-catch { - border: 5px solid var(--catch-detail-color); -} - -.border-attention { - border-color: var(--catch-detail-color); -} - -.direction-svg svg path { - fill: var(--catch-detail-color) !important; -} - .block-ruby { display: block ruby; } @@ -302,7 +228,10 @@ li::marker { display: none; } + .selected svg:not(.noselect *) path.selectable { + /* A marker on the map gets the 'selected' class when it's properties are displayed + */ stroke: white !important; stroke-width: 20px !important; overflow: visible !important; @@ -312,6 +241,8 @@ li::marker { } .selected svg { + /* A marker on the map gets the 'selected' class when it's properties are displayed + */ overflow: visible !important; } @@ -330,31 +261,7 @@ li::marker { .alert { background-color: var(--alert-color); - color: var(--foreground-color); - font-weight: bold; - border-radius: 1em; - margin: 0.25em; - text-align: center; - padding: 0.15em 0.3em; -} - -.invalid { - box-shadow: 0 0 10px #ff5353; - height: min-content; -} - -.shadow { - box-shadow: 0 0 20px var(--shadow-color); -} - -.title-font span { - font-size: xx-large !important; - font-weight: bold; -} - -.soft { - background-color: var(--subtle-detail-color); - color: var(--subtle-detail-color-contrast); + color: var(--alert-foreground-color); font-weight: bold; border-radius: 1em; margin: 0.25em; @@ -448,48 +355,3 @@ input { } } -.mapping-icon-small-height { - /* A mapping icon type */ - height: 1.5rem; - margin-right: 0.5rem; - width: unset; -} - -.mapping-icon-medium-height { - /* A mapping icon type */ - height: 3rem; - margin-right: 0.5rem; - width: unset; -} - -.mapping-icon-large-height { - /* A mapping icon type */ - height: 5rem; - margin-right: 0.5rem; - width: unset; -} - -.mapping-icon-small { - /* A mapping icon type */ - width: 1.5rem; - max-height: 1.5rem; - margin-right: 0.5rem; -} - -.mapping-icon-medium { - /* A mapping icon type */ - width: 3rem; - max-height: 3rem; - margin-right: 1rem; - margin-left: 1rem; -} - -.mapping-icon-large { - /* A mapping icon type */ - width: 6rem; - max-height: 5rem; - margin-top: 0.5rem; - margin-bottom: 0.5rem; - margin-right: 1.5rem; - margin-left: 1.5rem; -} From 052f685bbdd057a6a28e8cd6b184fae8c093058d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 9 May 2023 00:07:39 +0200 Subject: [PATCH 128/257] Refactoring: move css directory into 'public' --- {css => public/css}/ReviewElement.css | 0 {css => public/css}/index-tailwind-output.css | 0 {css => public/css}/mobile.css | 0 {css => public/css}/openinghourstable.css | 0 {css => public/css}/tagrendering.css | 0 {css => public/css}/wikipedia.css | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename {css => public/css}/ReviewElement.css (100%) rename {css => public/css}/index-tailwind-output.css (100%) rename {css => public/css}/mobile.css (100%) rename {css => public/css}/openinghourstable.css (100%) rename {css => public/css}/tagrendering.css (100%) rename {css => public/css}/wikipedia.css (100%) diff --git a/css/ReviewElement.css b/public/css/ReviewElement.css similarity index 100% rename from css/ReviewElement.css rename to public/css/ReviewElement.css diff --git a/css/index-tailwind-output.css b/public/css/index-tailwind-output.css similarity index 100% rename from css/index-tailwind-output.css rename to public/css/index-tailwind-output.css diff --git a/css/mobile.css b/public/css/mobile.css similarity index 100% rename from css/mobile.css rename to public/css/mobile.css diff --git a/css/openinghourstable.css b/public/css/openinghourstable.css similarity index 100% rename from css/openinghourstable.css rename to public/css/openinghourstable.css diff --git a/css/tagrendering.css b/public/css/tagrendering.css similarity index 100% rename from css/tagrendering.css rename to public/css/tagrendering.css diff --git a/css/wikipedia.css b/public/css/wikipedia.css similarity index 100% rename from css/wikipedia.css rename to public/css/wikipedia.css From 96544ec7850bd6f7b6ba446a8d4c988df0d668fd Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 9 May 2023 00:30:09 +0200 Subject: [PATCH 129/257] Chore: Reset mapcomplete-changes theme --- assets/themes/mapcomplete-changes/mapcomplete-changes.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index d8ec3f5b29..d7d8db5b86 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -286,10 +286,6 @@ "if": "theme=nature", "then": "./assets/themes/nature/logo.svg" }, - { - "if": "theme=natuurpunt", - "then": "./assets/themes/natuurpunt/natuurpunt.png" - }, { "if": "theme=notes", "then": "./assets/themes/notes/logo.svg" From a1f50322321d878d823ac3fc70021683dbb6ab69 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 9 May 2023 00:50:58 +0200 Subject: [PATCH 130/257] Fix: output tailwind into 'public/css/' instead of 'css/' --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4cd4d90007..c066670e14 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "strt": "vite --host", "strttest": "export NODE_OPTIONS=--max_old_space_size=8364 && parcel serve test.html assets/templates/*.svg assets/templates/fonts/*.ttf", "watch:css": "tailwindcss -i index.css -o css/index-tailwind-output.css --watch", - "generate:css": "tailwindcss -i index.css -o css/index-tailwind-output.css", + "generate:css": "tailwindcss -i index.css -o public/css/index-tailwind-output.css", "generate:doctests": "doctest-ts-improved . --ignore .*.spec.ts --ignore .*ConfigJson.ts", "test:run-only": "vitest --run test", "test": "npm run clean:tests && (npm run generate:doctests 2>&1 | grep -v \"No doctests found in\") && npm run test:run-only && npm run clean:tests", From 7f1e8d3f9cbaf65fa5b6bbb60fe59d1223883ff3 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 11 May 2023 02:17:41 +0200 Subject: [PATCH 131/257] Refactoring: overhaul of the visual style with CSS --- Logic/State/UserRelatedState.ts | 9 + Models/ThemeConfig/Conversion/PrepareLayer.ts | 1 + UI/Base/Loading.svelte | 2 +- UI/Base/MapControlButton.svelte | 4 +- UI/Base/SubtleButton.svelte | 48 +- UI/Base/SubtleButton.ts | 32 +- UI/Base/SubtleLink.svelte | 63 +++ UI/Base/TabbedGroup.svelte | 21 +- UI/BigComponents/CustomGeneratorButton.svelte | 32 +- UI/BigComponents/MoreScreen.ts | 45 -- UI/BigComponents/NoThemeResultButton.svelte | 5 +- .../ProfessionalServicesButton.svelte | 9 +- UI/BigComponents/ThemeButton.svelte | 188 +++---- UI/BigComponents/UnofficialThemeList.svelte | 2 + UI/Input/Checkboxes.ts | 9 +- UI/InputElement/Helpers/FloorSelector.svelte | 8 +- UI/InputElement/ValidatedInput.svelte | 1 + UI/InputElement/Validator.ts | 7 +- UI/InputElement/Validators/PhoneValidator.ts | 3 + UI/Popup/AddNewPoint/AddNewPoint.svelte | 2 +- UI/Popup/TagHint.svelte | 64 ++- UI/Popup/TagRendering/FreeformInput.svelte | 77 ++- UI/Popup/TagRendering/Questionbox.svelte | 2 +- .../TagRendering/TagRenderingEditable.svelte | 29 +- .../TagRenderingMappingInput.svelte | 2 +- .../TagRendering/TagRenderingQuestion.svelte | 81 +-- UI/SpecialVisualization.ts | 1 + UI/StylesheetTestGui.svelte | 90 ++- assets/layers/usersettings/license_info.json | 24 + .../usersettings/translate_disabled.svg | 59 ++ .../layers/usersettings/translate_mobile.svg | 50 ++ assets/svg/crosshair.svg | 4 +- assets/svg/min.svg | 4 +- assets/svg/plus.svg | 6 +- index.css | 514 +++++++++++------ package.json | 4 +- public/css/index-tailwind-output.css | 519 ++++++++++++------ 37 files changed, 1280 insertions(+), 741 deletions(-) create mode 100644 UI/Base/SubtleLink.svelte create mode 100644 assets/layers/usersettings/license_info.json create mode 100644 assets/layers/usersettings/translate_disabled.svg create mode 100644 assets/layers/usersettings/translate_mobile.svg diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index 8c9865cff5..304f09c2f4 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -14,6 +14,7 @@ import usersettings from "../../assets/generated/layers/usersettings.json" import Locale from "../../UI/i18n/Locale" import LinkToWeblate from "../../UI/Base/LinkToWeblate" import FeatureSwitchState from "./FeatureSwitchState" +import Constants from "../../Models/Constants"; /** * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, @@ -32,6 +33,9 @@ export default class UserRelatedState { public readonly installedUserThemes: Store public readonly showAllQuestionsAtOnce: UIEventSource + public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes">; + + public readonly homeLocation: FeatureSource /** @@ -88,6 +92,7 @@ export default class UserRelatedState { "Either 'true' or 'false'. If set, all questions will be shown all at once", }) ) + this.showTags = > this.osmConnection.GetPreference("show_tags") this.mangroveIdentity = new MangroveIdentity( this.osmConnection.GetLongPreference("identity", "mangrove") @@ -254,6 +259,10 @@ export default class UserRelatedState { _supports_sharing: window.navigator.share ? "yes" : "no" }) + for (const key in Constants.userJourney) { + amendedPrefs.data["__userjourney_"+key] = Constants.userJourney[key] + } + const osmConnection = this.osmConnection osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => { for (const k in newPrefs) { diff --git a/Models/ThemeConfig/Conversion/PrepareLayer.ts b/Models/ThemeConfig/Conversion/PrepareLayer.ts index fbb69c35c7..348949b70d 100644 --- a/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -637,6 +637,7 @@ export class AddEditingElements extends DesugaringStep { or: [ "__featureSwitchIsTesting=true", "__featureSwitchIsDebugging=true", + "mapcomplete-show_tags=full", "mapcomplete-show_debug=yes", ], }, diff --git a/UI/Base/Loading.svelte b/UI/Base/Loading.svelte index 2db2e44818..f98d9fe3f1 100644 --- a/UI/Base/Loading.svelte +++ b/UI/Base/Loading.svelte @@ -5,7 +5,7 @@
- +
diff --git a/UI/Base/MapControlButton.svelte b/UI/Base/MapControlButton.svelte index 915d8cf7b0..7528c0ba9b 100644 --- a/UI/Base/MapControlButton.svelte +++ b/UI/Base/MapControlButton.svelte @@ -8,6 +8,6 @@ -
dispatch("click", e)} class="subtle-background rounded-full h-fit w-fit m-0.5 md:m-1 p-0.5 sm:p-1 cursor-pointer"> +
+ diff --git a/UI/Base/SubtleButton.svelte b/UI/Base/SubtleButton.svelte index 88d6b233b1..9bbe097f13 100644 --- a/UI/Base/SubtleButton.svelte +++ b/UI/Base/SubtleButton.svelte @@ -1,73 +1,35 @@ - dispatch("click", e)} > {#if imageUrl !== undefined} {#if typeof imageUrl === "string"} - {:else } -
+ +

Stylesheet testing grounds

+ + This document exists to explore the style hierarchy. + +
+ +
+

Low interaction

+

+ There are low-interaction areas, where some buttons might appear. +

+ + + + + + +
+ +
+ +
+ + +
+ + Alert: something went wrong + Thank you! Operation successful +
+ +
+

Interactive area

+

+ There are interactive areas, where some buttons might appear. +

+ + + + + Alert: something went wrong + Thank you! Operation successful +
+ +