diff --git a/Customizations/AllKnownLayouts.ts b/Customizations/AllKnownLayouts.ts
index 47fbe45ee..7da19ba75 100644
--- a/Customizations/AllKnownLayouts.ts
+++ b/Customizations/AllKnownLayouts.ts
@@ -20,6 +20,11 @@ export class AllKnownLayouts {
public static AllPublicLayers() {
const allLayers: LayerConfig[] = []
const seendIds = new Set()
+ AllKnownLayouts.sharedLayers.forEach((layer, key) => {
+ seendIds.add(key)
+ allLayers.push(layer)
+ })
+
const publicLayouts = AllKnownLayouts.layoutsList.filter(l => !l.hideFromOverview)
for (const layout of publicLayouts) {
if (layout.hideFromOverview) {
@@ -34,6 +39,8 @@ export class AllKnownLayouts {
}
}
+
+
return allLayers
}
diff --git a/Docs/Misc/ImportANote.gif b/Docs/Misc/ImportANote.gif
deleted file mode 100644
index f41b96f63..000000000
Binary files a/Docs/Misc/ImportANote.gif and /dev/null differ
diff --git a/Docs/Misc/ImportAPoint.gif b/Docs/Misc/ImportAPoint.gif
new file mode 100644
index 000000000..e1b8ab366
Binary files /dev/null and b/Docs/Misc/ImportAPoint.gif differ
diff --git a/Docs/Screenshots/AED.png b/Docs/Screenshots/AED.png
new file mode 100644
index 000000000..da1c49f02
Binary files /dev/null and b/Docs/Screenshots/AED.png differ
diff --git a/Docs/Screenshots/AddNew.png b/Docs/Screenshots/AddNew.png
new file mode 100644
index 000000000..71065cc0e
Binary files /dev/null and b/Docs/Screenshots/AddNew.png differ
diff --git a/Docs/Screenshots/Cyclestreets.png b/Docs/Screenshots/Cyclestreets.png
new file mode 100644
index 000000000..d3281b03d
Binary files /dev/null and b/Docs/Screenshots/Cyclestreets.png differ
diff --git a/Docs/Screenshots/Cyclofix.png b/Docs/Screenshots/Cyclofix.png
new file mode 100644
index 000000000..61b162c3f
Binary files /dev/null and b/Docs/Screenshots/Cyclofix.png differ
diff --git a/Docs/Screenshots/Fritures.png b/Docs/Screenshots/Fritures.png
new file mode 100644
index 000000000..c91c3bd1c
Binary files /dev/null and b/Docs/Screenshots/Fritures.png differ
diff --git a/Docs/Screenshots/PinJePunt.png b/Docs/Screenshots/PinJePunt.png
new file mode 100644
index 000000000..ca4951f78
Binary files /dev/null and b/Docs/Screenshots/PinJePunt.png differ
diff --git a/Docs/Screenshots/Playground-popup-bottom.png b/Docs/Screenshots/Playground-popup-bottom.png
new file mode 100644
index 000000000..9c73ddb9c
Binary files /dev/null and b/Docs/Screenshots/Playground-popup-bottom.png differ
diff --git a/Docs/Screenshots/Playground-popup-top.png b/Docs/Screenshots/Playground-popup-top.png
new file mode 100644
index 000000000..e33e604d5
Binary files /dev/null and b/Docs/Screenshots/Playground-popup-top.png differ
diff --git a/Docs/Screenshots/Toilets.png b/Docs/Screenshots/Toilets.png
new file mode 100644
index 000000000..3d68f71dd
Binary files /dev/null and b/Docs/Screenshots/Toilets.png differ
diff --git a/Docs/Screenshots/collage.png b/Docs/Screenshots/collage.png
new file mode 100644
index 000000000..bb95dbef1
Binary files /dev/null and b/Docs/Screenshots/collage.png differ
diff --git a/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts b/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts
index 5349d8d43..1f5d8cbb4 100644
--- a/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts
+++ b/Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource.ts
@@ -1,5 +1,5 @@
/**
- * This feature source helps the ShowDataLayer class: it introduces the necessary extra features and indiciates with what renderConfig it should be rendered.
+ * This feature source helps the ShowDataLayer class: it introduces the necessary extra features and indicates with what renderConfig it should be rendered.
*/
import {UIEventSource} from "../../UIEventSource";
import {GeoOperations} from "../../GeoOperations";
@@ -11,22 +11,25 @@ export default class RenderingMultiPlexerFeatureSource {
public readonly features: UIEventSource<(any & { pointRenderingIndex: number | undefined, lineRenderingIndex: number | undefined })[]>;
constructor(upstream: FeatureSource, layer: LayerConfig) {
+
+ const pointRenderObjects: { rendering: PointRenderingConfig, index: number }[] = layer.mapRendering.map((r, i) => ({
+ rendering: r,
+ index: i
+ }))
+ const pointRenderings = pointRenderObjects.filter(r => r.rendering.location.has("point"))
+ const centroidRenderings = pointRenderObjects.filter(r => r.rendering.location.has("centroid"))
+ const projectedCentroidRenderings = pointRenderObjects.filter(r => r.rendering.location.has("projected_centerpoint"))
+ const startRenderings = pointRenderObjects.filter(r => r.rendering.location.has("start"))
+ const endRenderings = pointRenderObjects.filter(r => r.rendering.location.has("end"))
+ const hasCentroid = centroidRenderings.length > 0 || projectedCentroidRenderings.length > 0
+ const lineRenderObjects = layer.lineRendering
+
this.features = upstream.features.map(
features => {
if (features === undefined) {
return;
}
- const pointRenderObjects: { rendering: PointRenderingConfig, index: number }[] = layer.mapRendering.map((r, i) => ({
- rendering: r,
- index: i
- }))
- const pointRenderings = pointRenderObjects.filter(r => r.rendering.location.has("point"))
- const centroidRenderings = pointRenderObjects.filter(r => r.rendering.location.has("centroid"))
- const startRenderings = pointRenderObjects.filter(r => r.rendering.location.has("start"))
- const endRenderings = pointRenderObjects.filter(r => r.rendering.location.has("end"))
-
- const lineRenderObjects = layer.lineRendering
const withIndex: (any & { pointRenderingIndex: number | undefined, lineRenderingIndex: number | undefined, multiLineStringIndex: number | undefined })[] = [];
@@ -55,12 +58,25 @@ export default class RenderingMultiPlexerFeatureSource {
}
} else {
// This is a a line: add the centroids
- for (const rendering of centroidRenderings) {
- addAsPoint(feat, rendering, GeoOperations.centerpointCoordinates(feat))
+ let centerpoint: [number, number] = undefined;
+ let projectedCenterPoint : [number, number] = undefined
+ if(hasCentroid){
+ centerpoint = GeoOperations.centerpointCoordinates(feat)
+ if(projectedCentroidRenderings.length > 0){
+ projectedCenterPoint = <[number,number]> GeoOperations.nearestPoint(feat, centerpoint).geometry.coordinates
+ }
}
+ for (const rendering of centroidRenderings) {
+ addAsPoint(feat, rendering, centerpoint)
+ }
+
if (feat.geometry.type === "LineString") {
+ for (const rendering of projectedCentroidRenderings) {
+ addAsPoint(feat, rendering, projectedCenterPoint)
+ }
+
// Add start- and endpoints
const coordinates = feat.geometry.coordinates
for (const rendering of startRenderings) {
@@ -71,6 +87,10 @@ export default class RenderingMultiPlexerFeatureSource {
addAsPoint(feat, rendering, coordinate)
}
+ }else{
+ for (const rendering of projectedCentroidRenderings) {
+ addAsPoint(feat, rendering, centerpoint)
+ }
}
// AT last, add it 'as is' to what we should render
diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts
index 73f553dcf..df99a707a 100644
--- a/Logic/GeoOperations.ts
+++ b/Logic/GeoOperations.ts
@@ -24,6 +24,10 @@ export class GeoOperations {
return newFeature;
}
+ /**
+ * Returns [lon,lat] coordinates
+ * @param feature
+ */
static centerpointCoordinates(feature: any): [number, number] {
return <[number, number]>turf.center(feature).geometry.coordinates;
}
diff --git a/Logic/Osm/Geocoding.ts b/Logic/Osm/Geocoding.ts
index 55dd99681..7f8749c50 100644
--- a/Logic/Osm/Geocoding.ts
+++ b/Logic/Osm/Geocoding.ts
@@ -1,23 +1,22 @@
import State from "../../State";
import {Utils} from "../../Utils";
+import {BBox} from "../BBox";
+
+export interface GeoCodeResult {
+ display_name: string,
+ lat: number, lon: number, boundingbox: number[],
+ osm_type: string, osm_id: string
+}
export class Geocoding {
private static readonly host = "https://nominatim.openstreetmap.org/search?";
- static Search(query: string,
- handleResult: ((places: {
- display_name: string, lat: number, lon: number, boundingbox: number[],
- osm_type: string, osm_id: string
- }[]) => void),
- onFail: (() => void)) {
- const b = State.state.currentBounds.data;
+ static async Search(query: string): Promise {
+ const b = State?.state?.currentBounds?.data ?? BBox.global;
const url = Geocoding.host + "format=json&limit=1&viewbox=" +
`${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}` +
"&accept-language=nl&q=" + query;
- Utils.downloadJson(
- url)
- .then(handleResult)
- .catch(onFail);
+ return Utils.downloadJson(url)
}
}
diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts
index 93164ca2f..5b8408583 100644
--- a/Logic/Osm/OsmConnection.ts
+++ b/Logic/Osm/OsmConnection.ts
@@ -1,4 +1,3 @@
-// @ts-ignore
import osmAuth from "osm-auth";
import {UIEventSource} from "../UIEventSource";
import {OsmPreferences} from "./OsmPreferences";
@@ -222,7 +221,7 @@ export class OsmConnection {
});
}
- public closeNote(id: number | string, text?: string): Promise {
+ public closeNote(id: number | string, text?: string): Promise {
let textSuffix = ""
if ((text ?? "") !== "") {
textSuffix = "?text=" + encodeURIComponent(text)
@@ -249,7 +248,7 @@ export class OsmConnection {
}
- public reopenNote(id: number | string, text?: string): Promise {
+ public reopenNote(id: number | string, text?: string): Promise {
if (this._dryRun.data) {
console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text)
return new Promise((ok, error) => {
@@ -313,7 +312,7 @@ export class OsmConnection {
}
- public addCommentToNode(id: number | string, text: string): Promise {
+ public addCommentToNode(id: number | string, text: string): Promise {
if (this._dryRun.data) {
console.warn("Dryrun enabled - not actually adding comment ", text, "to note ", id)
return new Promise((ok, error) => {
diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts
index bb5a8e997..f0ab35407 100644
--- a/Logic/Osm/OsmObject.ts
+++ b/Logic/Osm/OsmObject.ts
@@ -69,7 +69,7 @@ export abstract class OsmObject {
return rawData.elements[0].tags
}
- static async DownloadObjectAsync(id: string): Promise {
+ static async DownloadObjectAsync(id: string): Promise {
const splitted = id.split("/");
const type = splitted[0];
const idN = Number(splitted[1]);
@@ -80,6 +80,9 @@ export abstract class OsmObject {
const full = (id.startsWith("way")) ? "/full" : "";
const url = `${OsmObject.backendURL}api/0.6/${id}${full}`;
const rawData = await Utils.downloadJsonCached(url, 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
diff --git a/Logic/Osm/Overpass.ts b/Logic/Osm/Overpass.ts
index 5463770ac..a563c31bf 100644
--- a/Logic/Osm/Overpass.ts
+++ b/Logic/Osm/Overpass.ts
@@ -4,8 +4,10 @@ import {Utils} from "../../Utils";
import {UIEventSource} from "../UIEventSource";
import {BBox} from "../BBox";
import * as osmtogeojson from "osmtogeojson";
-// @ts-ignore
-import {Tag} from "../Tags/Tag"; // used in doctest
+<<<<<<< HEAD
+
+=======
+>>>>>>> b54b5061cc72488ceb007177275fb600cce0a0dd
/**
* Interfaces overpass to get all the latest data
@@ -58,6 +60,10 @@ export class Overpass {
}
/**
+ * Constructs the actual script
+ *
+ * import {Tag} from "../Tags/Tag";
+ *
* new Overpass(new Tag("key","value"), [], "").buildScript("{{bbox}}") // => `[out:json][timeout:90]{{bbox}};(nwr["key"="value"];);out body;out meta;>;out skel qt;`
*/
public buildScript(bbox: string, postCall: string = "", pretty = false): string {
diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts
index af00de028..714da8e75 100644
--- a/Logic/SimpleMetaTagger.ts
+++ b/Logic/SimpleMetaTagger.ts
@@ -7,6 +7,7 @@ import Title from "../UI/Base/Title";
import {FixedUiElement} from "../UI/Base/FixedUiElement";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import {CountryCoder} from "latlon2country"
+import Constants from "../Models/Constants";
export class SimpleMetaTagger {
@@ -40,7 +41,7 @@ export class SimpleMetaTagger {
}
export class CountryTagger extends SimpleMetaTagger {
- private static readonly coder = new CountryCoder("https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country", Utils.downloadJson);
+ private static readonly coder = new CountryCoder(Constants.countryCoderEndpoint, Utils.downloadJson);
public runningTasks: Set;
constructor() {
@@ -217,7 +218,7 @@ export default class SimpleMetaTaggers {
},
((feature, _, __, state) => {
- const units = Utils.NoNull([].concat(...state?.layoutToUse?.layers?.map(layer => layer.units ?? [])));
+ const units = Utils.NoNull([].concat(...state?.layoutToUse?.layers?.map(layer => layer.units )?? []));
if (units.length == 0) {
return;
}
diff --git a/Logic/Web/Wikidata.ts b/Logic/Web/Wikidata.ts
index 936475001..68f7242a3 100644
--- a/Logic/Web/Wikidata.ts
+++ b/Logic/Web/Wikidata.ts
@@ -1,6 +1,6 @@
import {Utils} from "../../Utils";
import {UIEventSource} from "../UIEventSource";
-import * as wds from "wikibase-sdk"
+import * as wds from "wikidata-sdk"
export class WikidataResponse {
public readonly id: string
@@ -126,13 +126,22 @@ export interface WikidataSearchoptions {
maxCount?: 20 | number
}
+export interface WikidataAdvancedSearchoptions extends WikidataSearchoptions {
+ instanceOf?: number[];
+ notInstanceOf?: number[]
+}
+
+
/**
* Utility functions around wikidata
*/
export default class Wikidata {
private static readonly _identifierPrefixes = ["Q", "L"].map(str => str.toLowerCase())
- private static readonly _prefixesToRemove = ["https://www.wikidata.org/wiki/Lexeme:", "https://www.wikidata.org/wiki/", "Lexeme:"].map(str => str.toLowerCase())
+ private static readonly _prefixesToRemove = ["https://www.wikidata.org/wiki/Lexeme:",
+ "https://www.wikidata.org/wiki/",
+ "http://www.wikidata.org/entity/",
+ "Lexeme:"].map(str => str.toLowerCase())
private static readonly _cache = new Map>()
@@ -147,25 +156,51 @@ export default class Wikidata {
Wikidata._cache.set(key, src)
return src;
}
-
- public static async searchAdvanced(text: string, options: WikidataSearchoptions & {
- instanceOf: number}){
+
+ /**
+ * Given a search text, searches for the relevant wikidata entries, excluding pages "outside of the main tree", e.g. disambiguation pages.
+ * Optionally, an 'instance of' can be given to limit the scope, e.g. instanceOf:5 (humans) will only search for humans
+ */
+ public static async searchAdvanced(text: string, options: WikidataAdvancedSearchoptions): Promise<{
+ id: string,
+ relevance?: number,
+ label: string,
+ description?: string
+ }[]> {
+ let instanceOf = ""
+ if (options?.instanceOf !== undefined && options.instanceOf.length > 0) {
+ const phrases = options.instanceOf.map(q => `{ ?item wdt:P31/wdt:P279* wd:Q${q}. }`)
+ instanceOf = "{"+ phrases.join(" UNION ") + "}"
+ }
+ const forbidden = (options?.notInstanceOf ?? [])
+ .concat([17379835]) // blacklist 'wikimedia pages outside of the main knowledge tree', e.g. disambiguation pages
+ const minusPhrases = forbidden.map(q => `MINUS {?item wdt:P31/wdt:P279* wd:Q${q} .}`)
const sparql = `SELECT * WHERE {
SERVICE wikibase:mwapi {
bd:serviceParam wikibase:api "EntitySearch" .
- bd:serviceParam wikibase:endpoint "www.wikidata.org" .
- bd:serviceParam mwapi:search "${text}" .
- bd:serviceParam mwapi:language "${options.lang}" .
- ?item wikibase:apiOutputItem mwapi:item .
- ?num wikibase:apiOrdinal true .
- }
- ?item (wdt:P279|wdt:P31) wd:Q${options.instanceOf}
- } ORDER BY ASC(?num) LIMIT ${options.maxCount}`
+ bd:serviceParam wikibase:endpoint "www.wikidata.org" .
+ bd:serviceParam mwapi:search "${text}" .
+ bd:serviceParam mwapi:language "${options.lang}" .
+ ?item wikibase:apiOutputItem mwapi:item .
+ ?num wikibase:apiOrdinal true .
+ bd:serviceParam wikibase:limit ${Math.round((options.maxCount ?? 20) * 1.5) /*Some padding for disambiguation pages */} .
+ ?label wikibase:apiOutput mwapi:label .
+ ?description wikibase:apiOutput "@description" .
+ }
+ ${instanceOf}
+ ${minusPhrases.join("\n ")}
+ } ORDER BY ASC(?num) LIMIT ${options.maxCount ?? 20}`
const url = wds.sparqlQuery(sparql)
- const result = await Utils.downloadJson(url, {"User-Agent": "MapComplete script"})
- return result.results.bindings
-
+ const result = await Utils.downloadJson(url)
+ /*The full uri of the wikidata-item*/
+
+ return result.results.bindings.map(({item, label, description, num}) => ({
+ relevance: num?.value,
+ id: item?.value,
+ label: label?.value,
+ description: description?.value
+ }))
}
public static async search(
@@ -215,39 +250,28 @@ export default class Wikidata {
public static async searchAndFetch(
search: string,
- options?: WikidataSearchoptions
+ options?: WikidataAdvancedSearchoptions
): Promise {
- const maxCount = options.maxCount
// We provide some padding to filter away invalid values
- options.maxCount = Math.ceil((options.maxCount ?? 20) * 1.5)
- const searchResults = await Wikidata.search(search, options)
- const maybeResponses = await Promise.all(searchResults.map(async r => {
- try {
- return await Wikidata.LoadWikidataEntry(r.id).AsPromise()
- } catch (e) {
- console.error(e)
- return undefined;
- }
- }))
- const responses = maybeResponses
- .map(r => r["success"])
- .filter(wd => {
- if (wd === undefined) {
- return false;
+ const searchResults = await Wikidata.searchAdvanced(search, options)
+ const maybeResponses = await Promise.all(
+ searchResults.map(async r => {
+ try {
+ console.log("Loading ", r.id)
+ return await Wikidata.LoadWikidataEntry(r.id).AsPromise()
+ } catch (e) {
+ console.error(e)
+ return undefined;
}
- if (wd.claims.get("P31" /*Instance of*/)?.has("Q4167410"/* Wikimedia Disambiguation page*/)) {
- return false;
- }
- return true;
- })
- responses.splice(maxCount, responses.length - maxCount)
- return responses
+ }))
+ return Utils.NoNull(maybeResponses.map(r => r["success"]))
}
/**
* Gets the 'key' segment from a URL
- *
+ *
* Wikidata.ExtractKey("https://www.wikidata.org/wiki/Lexeme:L614072") // => "L614072"
+ * Wikidata.ExtractKey("http://www.wikidata.org/entity/Q55008046") // => "Q55008046"
*/
public static ExtractKey(value: string | number): string {
if (typeof value === "number") {
@@ -291,6 +315,35 @@ export default class Wikidata {
return undefined;
}
+ /**
+ * Converts 'Q123' into 123, returns undefined if invalid
+ *
+ * Wikidata.QIdToNumber("Q123") // => 123
+ * Wikidata.QIdToNumber(" Q123 ") // => 123
+ * Wikidata.QIdToNumber(" X123 ") // => undefined
+ * Wikidata.QIdToNumber(" Q123X ") // => undefined
+ * Wikidata.QIdToNumber(undefined) // => undefined
+ * Wikidata.QIdToNumber(123) // => 123
+ */
+ public static QIdToNumber(q: string | number): number | undefined {
+ if(q === undefined || q === null){
+ return
+ }
+ if(typeof q === "number"){
+ return q
+ }
+ q = q.trim()
+ if (!q.startsWith("Q")) {
+ return
+ }
+ q = q.substr(1)
+ const n = Number(q)
+ if (isNaN(n)) {
+ return
+ }
+ return n
+ }
+
public static IdToArticle(id: string) {
if (id.startsWith("Q")) {
return "https://wikidata.org/wiki/" + id
@@ -309,7 +362,7 @@ export default class Wikidata {
const id = Wikidata.ExtractKey(value)
if (id === undefined) {
console.warn("Could not extract a wikidata entry from", value)
- throw "Could not extract a wikidata entry from " + value
+ return undefined
}
const url = "https://www.wikidata.org/wiki/Special:EntityData/" + id + ".json";
@@ -325,4 +378,4 @@ export default class Wikidata {
return WikidataResponse.fromJson(response)
}
-}
\ No newline at end of file
+}
diff --git a/Models/Constants.ts b/Models/Constants.ts
index e829a39de..73c937293 100644
--- a/Models/Constants.ts
+++ b/Models/Constants.ts
@@ -2,7 +2,7 @@ import {Utils} from "../Utils";
export default class Constants {
- public static vNumber = "0.18.2";
+ public static vNumber = "0.19.0-alpha";
public static ImgurApiKey = '7070e7167f0a25a'
public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85"
@@ -69,6 +69,7 @@ export default class Constants {
* In seconds
*/
static zoomToLocationTimeout = 60;
+ static countryCoderEndpoint: string = "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country";
private static isRetina(): boolean {
if (Utils.runningFromConsole) {
diff --git a/Models/ThemeConfig/Conversion/Conversion.ts b/Models/ThemeConfig/Conversion/Conversion.ts
index 3d9aa1183..5b6b72c00 100644
--- a/Models/ThemeConfig/Conversion/Conversion.ts
+++ b/Models/ThemeConfig/Conversion/Conversion.ts
@@ -216,13 +216,18 @@ export class Fuse extends DesugaringStep {
const information = []
for (let i = 0; i < this.steps.length; i++) {
const step = this.steps[i];
- let r = step.convert(json, "While running step " + step.name + ": " + context)
- errors.push(...r.errors ?? [])
- warnings.push(...r.warnings ?? [])
- information.push(...r.information ?? [])
- json = r.result
- if (errors.length > 0) {
- break;
+ try{
+ let r = step.convert(json, "While running step " + step.name + ": " + context)
+ errors.push(...r.errors ?? [])
+ warnings.push(...r.warnings ?? [])
+ information.push(...r.information ?? [])
+ json = r.result
+ if (errors.length > 0) {
+ break;
+ }
+ }catch(e){
+ console.error("Step "+step.name+" failed due to "+e);
+ throw e
}
}
return {
diff --git a/Models/ThemeConfig/Conversion/PrepareTheme.ts b/Models/ThemeConfig/Conversion/PrepareTheme.ts
index 7c1aa1721..1d61f2a3b 100644
--- a/Models/ThemeConfig/Conversion/PrepareTheme.ts
+++ b/Models/ThemeConfig/Conversion/PrepareTheme.ts
@@ -330,12 +330,19 @@ class AddDependencyLayersToTheme extends DesugaringStep {
private readonly _state: DesugaringContext;
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)", ["layers"], "AddDependencyLayersToTheme");
+ 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.
+ `, ["layers"], "AddDependencyLayersToTheme");
this._state = state;
}
- private static CalculateDependencies(alreadyLoaded: LayerConfigJson[], allKnownLayers: Map, themeId: string): LayerConfigJson[] {
- const dependenciesToAdd: LayerConfigJson[] = []
+ private static CalculateDependencies(alreadyLoaded: LayerConfigJson[], allKnownLayers: Map, themeId: string):
+ {config: LayerConfigJson, reason: string}[] {
+ const dependenciesToAdd: {config: LayerConfigJson, reason: string}[] = []
const loadedLayerIds: Set = new Set(alreadyLoaded.map(l => l.id));
// Verify cross-dependencies
@@ -361,35 +368,39 @@ class AddDependencyLayersToTheme extends DesugaringStep {
}
// During the generate script, builtin layers are verified but not loaded - so we have to add them manually here
- // Their existance is checked elsewhere, so this is fine
+ // Their existence is checked elsewhere, so this is fine
unmetDependencies = dependencies.filter(dep => !loadedLayerIds.has(dep.neededLayer))
for (const unmetDependency of unmetDependencies) {
if (loadedLayerIds.has(unmetDependency.neededLayer)) {
continue
}
- const dep = allKnownLayers.get(unmetDependency.neededLayer)
+ const dep = Utils.Clone(allKnownLayers.get(unmetDependency.neededLayer))
+ const reason = "This layer is needed by " + unmetDependency.neededBy +" because " +
+ unmetDependency.reason + " (at " + unmetDependency.context + ")";
if (dep === undefined) {
const message =
["Loading a dependency failed: layer " + unmetDependency.neededLayer + " is not found, neither as layer of " + themeId + " nor as builtin layer.",
- "This layer is needed by " + unmetDependency.neededBy,
- unmetDependency.reason + " (at " + unmetDependency.context + ")",
+ reason,
"Loaded layers are: " + alreadyLoaded.map(l => l.id).join(",")
]
throw message.join("\n\t");
}
- dependenciesToAdd.unshift(dep)
+
+ dep.forceLoad = true;
+ dep.passAllFeatures = true;
+ dep.description = reason;
+ dependenciesToAdd.unshift({
+ config: dep,
+ reason
+ })
loadedLayerIds.add(dep.id);
unmetDependencies = unmetDependencies.filter(d => d.neededLayer !== unmetDependency.neededLayer)
}
} while (unmetDependencies.length > 0)
- return dependenciesToAdd.map(dep => {
- dep = Utils.Clone(dep);
- dep.forceLoad = true
- return dep
- });
+ return dependenciesToAdd
}
convert(theme: LayoutConfigJson, context: string): { result: LayoutConfigJson; information: string[] } {
@@ -404,11 +415,16 @@ class AddDependencyLayersToTheme extends DesugaringStep {
})
const dependencies = AddDependencyLayersToTheme.CalculateDependencies(layers, allKnownLayers, theme.id);
- if (dependencies.length > 0) {
-
- information.push(context + ": added " + dependencies.map(d => d.id).join(", ") + " to the theme as they are needed")
+ for (const dependency of dependencies) {
+
}
- layers.unshift(...dependencies);
+ if (dependencies.length > 0) {
+ for (const dependency of dependencies) {
+ information.push(context + ": added " + dependency.config.id + " to the theme. "+dependency.reason)
+
+ }
+ }
+ layers.unshift(...dependencies.map(l => l.config));
return {
result: {
@@ -479,6 +495,7 @@ export class PrepareTheme extends Fuse {
}) {
super(
"Fully prepares and expands a theme",
+
new AddContextToTransltionsInLayout(),
new PreparePersonalTheme(state),
new WarnForUnsubstitutedLayersInTheme(),
diff --git a/Models/ThemeConfig/Conversion/Validation.ts b/Models/ThemeConfig/Conversion/Validation.ts
index 912707877..b0c121953 100644
--- a/Models/ThemeConfig/Conversion/Validation.ts
+++ b/Models/ThemeConfig/Conversion/Validation.ts
@@ -217,12 +217,17 @@ class MiscThemeChecks extends DesugaringStep{
convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
const warnings = []
+ const errors = []
+ if(json.id !== "personal" && (json.layers === undefined || json.layers.length === 0)){
+ errors.push("The theme "+json.id+" has no 'layers' defined ("+context+")")
+ }
if(json.socialImage === ""){
warnings.push("Social image for theme "+json.id+" is the emtpy string")
}
return {
result :json,
- warnings
+ warnings,
+ errors
};
}
}
@@ -231,8 +236,8 @@ export class PrevalidateTheme extends Fuse {
constructor() {
super("Various consistency checks on the raw JSON",
- new OverrideShadowingCheck(),
- new MiscThemeChecks()
+ new MiscThemeChecks(),
+ new OverrideShadowingCheck()
);
}
diff --git a/Models/ThemeConfig/DependencyCalculator.ts b/Models/ThemeConfig/DependencyCalculator.ts
index c4d3663d3..895f26779 100644
--- a/Models/ThemeConfig/DependencyCalculator.ts
+++ b/Models/ThemeConfig/DependencyCalculator.ts
@@ -81,7 +81,7 @@ export default class DependencyCalculator {
// The important line: steal the dependencies!
deps.push({
- neededLayer: layerId, reason: "A calculated tag loads features from this layer",
+ neededLayer: layerId, reason: "a calculated tag loads features from this layer",
context: "calculatedTag[" + currentLine + "] which calculates the value for " + currentKey,
neededBy: layer.id
})
diff --git a/Models/ThemeConfig/Json/PointRenderingConfigJson.ts b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts
index 5170f8953..c9a4abc9d 100644
--- a/Models/ThemeConfig/Json/PointRenderingConfigJson.ts
+++ b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts
@@ -13,9 +13,10 @@ export default interface PointRenderingConfigJson {
/**
* All the locations that this point should be rendered at.
- * Using `location: ["point", "centroid"] will always render centerpoint
+ * 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)
*/
- location: ("point" | "centroid" | "start" | "end" | string)[]
+ location: ("point" | "centroid" | "start" | "end" | "projected_centerpoint" | string)[]
/**
* The icon for an element.
diff --git a/Models/ThemeConfig/Json/UnitConfigJson.ts b/Models/ThemeConfig/Json/UnitConfigJson.ts
index 5eba5eaf4..bde2683b2 100644
--- a/Models/ThemeConfig/Json/UnitConfigJson.ts
+++ b/Models/ThemeConfig/Json/UnitConfigJson.ts
@@ -18,9 +18,12 @@ export default interface UnitConfigJson {
export interface ApplicableUnitJson {
/**
- * The canonical value which will be added to the text.
+ * The canonical value which will be added to the value in OSM.
* e.g. "m" for meters
- * If the user inputs '42', the canonical value will be added and it'll become '42m'
+ * If the user inputs '42', the canonical value will be added and it'll become '42m'.
+ *
+ * Important: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.
+ * In this case, an empty string should be used
*/
canonicalDenomination: string,
/**
diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts
index b7863b427..ee9063cfa 100644
--- a/Models/ThemeConfig/LayerConfig.ts
+++ b/Models/ThemeConfig/LayerConfig.ts
@@ -27,7 +27,6 @@ import FilterConfigJson from "./Json/FilterConfigJson";
import {And} from "../../Logic/Tags/And";
import {Overpass} from "../../Logic/Osm/Overpass";
import Constants from "../Constants";
-import undefinedError = Mocha.utils.undefinedError;
export default class LayerConfig extends WithContextLoader {
@@ -134,6 +133,9 @@ export default class LayerConfig extends WithContextLoader {
this.allowSplit = json.allowSplit ?? false;
this.name = Translations.T(json.name, translationContext + ".name");
+ if(json.units!==undefined && !Array.isArray(json.units)){
+ throw "At "+context+".units: the 'units'-section should be a list; you probably have an object there"
+ }
this.units = (json.units ?? []).map(((unitJson, i) => Unit.fromJson(unitJson, `${context}.unit[${i}]`)))
if (json.description !== undefined) {
@@ -158,7 +160,11 @@ export default class LayerConfig extends WithContextLoader {
this.calculatedTags = [];
for (const kv of json.calculatedTags) {
const index = kv.indexOf("=");
- let key = kv.substring(0, index);
+ let key = kv.substring(0, index).trim();
+ const r = "[a-z_][a-z0-9:]*"
+ if(key.match(r) === null){
+ throw "At "+context+" invalid key for calculated tag: "+key+"; it should match "+r
+ }
const isStrict = key.endsWith(':')
if (isStrict) {
key = key.substr(0, key.length - 1)
@@ -331,11 +337,12 @@ export default class LayerConfig extends WithContextLoader {
return TagUtils.changeAsProperties(this.source.osmTags.asChange({id: "node/-1"}))
}
- public GenerateDocumentation(usedInThemes: string[], layerIsNeededBy: Map, dependencies: {
+ public GenerateDocumentation(usedInThemes: string[], layerIsNeededBy?: Map, dependencies: {
context?: string;
reason: string;
neededLayer: string;
- }[], addedByDefault = false, canBeIncluded = true): BaseUIElement {
+ }[] = []
+ , addedByDefault = false, canBeIncluded = true): BaseUIElement {
const extraProps = []
extraProps.push("This layer is shown at zoomlevel **"+this.minzoom+"** and higher")
diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts
index 0a02149b8..39dcec156 100644
--- a/Models/ThemeConfig/PointRenderingConfig.ts
+++ b/Models/ThemeConfig/PointRenderingConfig.ts
@@ -15,8 +15,8 @@ import {VariableUiElement} from "../../UI/Base/VariableUIElement";
export default class PointRenderingConfig extends WithContextLoader {
- private static readonly allowed_location_codes = new Set(["point", "centroid", "start", "end"])
- public readonly location: Set<"point" | "centroid" | "start" | "end" | string>
+ private static readonly allowed_location_codes = new Set(["point", "centroid", "start", "end","projected_centerpoint"])
+ public readonly location: Set<"point" | "centroid" | "start" | "end" | "projected_centerpoint" | string>
public readonly icon: TagRenderingConfig;
public readonly iconBadges: { if: TagsFilter; then: TagRenderingConfig }[];
diff --git a/UI/AllTagsPanel.ts b/UI/AllTagsPanel.ts
new file mode 100644
index 000000000..7a6322f67
--- /dev/null
+++ b/UI/AllTagsPanel.ts
@@ -0,0 +1,46 @@
+import {VariableUiElement} from "./Base/VariableUIElement";
+import {UIEventSource} from "../Logic/UIEventSource";
+import Table from "./Base/Table";
+
+export class AllTagsPanel extends VariableUiElement {
+
+ constructor(tags: UIEventSource, state?) {
+
+ const calculatedTags = [].concat(
+ // SimpleMetaTagger.lazyTags,
+ ...(state?.layoutToUse?.layers?.map(l => l.calculatedTags?.map(c => c[0]) ?? []) ?? []))
+
+
+ super(tags.map(tags => {
+ const parts = [];
+ for (const key in tags) {
+ if (!tags.hasOwnProperty(key)) {
+ continue
+ }
+ let v = tags[key]
+ if (v === "") {
+ v = "empty string "
+ }
+ parts.push([key, v ?? "undefined "]);
+ }
+
+ for (const key of calculatedTags) {
+ const value = tags[key]
+ if (value === undefined) {
+ continue
+ }
+ let type = "";
+ if (typeof value !== "string") {
+ type = " " + (typeof value) + " "
+ }
+ parts.push(["" + key + " ", value])
+ }
+
+ return new Table(
+ ["key", "value"],
+ parts
+ )
+ .SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;").SetClass("zebra-table")
+ }))
+ }
+}
\ No newline at end of file
diff --git a/UI/Base/VariableUIElement.ts b/UI/Base/VariableUIElement.ts
index 55538f2ce..13f147a4c 100644
--- a/UI/Base/VariableUIElement.ts
+++ b/UI/Base/VariableUIElement.ts
@@ -16,7 +16,7 @@ export class VariableUiElement extends BaseUIElement {
}
AsMarkdown(): string {
- const d = this._contents.data;
+ const d = this._contents?.data;
if (typeof d === "string") {
return d;
}
@@ -29,7 +29,7 @@ export class VariableUiElement extends BaseUIElement {
protected InnerConstructElement(): HTMLElement {
const el = document.createElement("span");
const self = this;
- this._contents.addCallbackAndRun((contents) => {
+ this._contents?.addCallbackAndRun((contents) => {
if (self.isDestroyed) {
return true;
}
diff --git a/UI/BaseUIElement.ts b/UI/BaseUIElement.ts
index d01b72867..556ab637f 100644
--- a/UI/BaseUIElement.ts
+++ b/UI/BaseUIElement.ts
@@ -94,7 +94,7 @@ export default abstract class BaseUIElement {
* The same as 'Render', but creates a HTML element instead of the HTML representation
*/
public ConstructElement(): HTMLElement {
- if (Utils.runningFromConsole) {
+ if (typeof window === undefined) {
return undefined;
}
diff --git a/UI/BigComponents/FilterView.ts b/UI/BigComponents/FilterView.ts
index e707211a0..01f4ff29f 100644
--- a/UI/BigComponents/FilterView.ts
+++ b/UI/BigComponents/FilterView.ts
@@ -20,6 +20,7 @@ import {QueryParameters} from "../../Logic/Web/QueryParameters";
import {TagUtils} from "../../Logic/Tags/TagUtils";
import {InputElement} from "../Input/InputElement";
import {DropDown} from "../Input/DropDown";
+import {UIElement} from "../UIElement";
export default class FilterView extends VariableUiElement {
constructor(filteredLayer: UIEventSource,
@@ -33,7 +34,7 @@ export default class FilterView extends VariableUiElement {
filteredLayer.map((filteredLayers) => {
// Create the views which toggle layers (and filters them) ...
let elements = filteredLayers
- ?.map(l => FilterView.createOneFilteredLayerElement(l)?.SetClass("filter-panel"))
+ ?.map(l => FilterView.createOneFilteredLayerElement(l, State.state)?.SetClass("filter-panel"))
?.filter(l => l !== undefined)
elements[0].SetClass("first-filter-panel")
@@ -87,10 +88,14 @@ export default class FilterView extends VariableUiElement {
);
}
- private static createOneFilteredLayerElement(filteredLayer: FilteredLayer) {
+ private static createOneFilteredLayerElement(filteredLayer: FilteredLayer, state: {featureSwitchIsDebugging: UIEventSource}) {
if (filteredLayer.layerDef.name === undefined) {
// Name is not defined: we hide this one
- return undefined;
+ return new Toggle(
+ filteredLayer?.layerDef?.description?.Clone()?.SetClass("subtle") ,
+ undefined,
+ state?.featureSwitchIsDebugging
+ );
}
const iconStyle = "width:1.5rem;height:1.5rem;margin-left:1.25rem;flex-shrink: 0;";
diff --git a/UI/BigComponents/SearchAndGo.ts b/UI/BigComponents/SearchAndGo.ts
index a0cb766bd..44efc2d10 100644
--- a/UI/BigComponents/SearchAndGo.ts
+++ b/UI/BigComponents/SearchAndGo.ts
@@ -44,38 +44,37 @@ export default class SearchAndGo extends Combine {
);
// Triggered by 'enter' or onclick
- function runSearch() {
+ async function runSearch() {
const searchString = searchField.GetValue().data;
if (searchString === undefined || searchString === "") {
return;
}
searchField.GetValue().setData("");
placeholder.setData(Translations.t.general.search.searching);
- Geocoding.Search(
- searchString,
- (result) => {
- console.log("Search result", result);
- if (result.length == 0) {
- placeholder.setData(Translations.t.general.search.nothing);
- return;
- }
+ try {
- 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);
- },
- () => {
- searchField.GetValue().setData("");
- placeholder.setData(Translations.t.general.search.error);
+ const result = await Geocoding.Search(searchString);
+
+ 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);
diff --git a/UI/Image/DeleteImage.ts b/UI/Image/DeleteImage.ts
index 03f8d7d9b..3f1690244 100644
--- a/UI/Image/DeleteImage.ts
+++ b/UI/Image/DeleteImage.ts
@@ -55,7 +55,7 @@ export default class DeleteImage extends Toggle {
tags.map(tags => (tags[key] ?? "") !== "")
),
undefined /*Login (and thus editing) is disabled*/,
- state.osmConnection.isLoggedIn
+ state?.osmConnection?.isLoggedIn
)
this.SetClass("cursor-pointer")
}
diff --git a/UI/Image/ImageUploadFlow.ts b/UI/Image/ImageUploadFlow.ts
index 47e775289..807e5464c 100644
--- a/UI/Image/ImageUploadFlow.ts
+++ b/UI/Image/ImageUploadFlow.ts
@@ -112,7 +112,7 @@ export class ImageUploadFlow extends Toggle {
}
- const title = matchingLayer?.title?.GetRenderValue(tags)?.ConstructElement()?.innerText ?? tags.name ?? "Unknown area";
+ const title = matchingLayer?.title?.GetRenderValue(tags)?.Subs(tags)?.ConstructElement()?.innerText ?? tags.name ?? "https//osm.org/"+tags.id;
const description = [
"author:" + state.osmConnection.userDetails.data.name,
"license:" + license,
@@ -169,7 +169,7 @@ export class ImageUploadFlow extends Toggle {
state?.osmConnection?.isLoggedIn
),
undefined /* Nothing as the user badge is disabled*/,
- state.featureSwitchUserbadge
+ state?.featureSwitchUserbadge
)
}
diff --git a/UI/ImportFlow/ImportViewerGui.ts b/UI/ImportFlow/ImportViewerGui.ts
index 87742277b..9ac79ebdb 100644
--- a/UI/ImportFlow/ImportViewerGui.ts
+++ b/UI/ImportFlow/ImportViewerGui.ts
@@ -44,6 +44,47 @@ interface NoteState {
status: "imported" | "already_mapped" | "invalid" | "closed" | "not_found" | "open" | "has_comments"
}
+class DownloadStatisticsButton extends SubtleButton {
+ constructor(states: NoteState[][]) {
+ super(Svg.statistics_svg(), "Download statistics");
+ this.onClick(() => {
+
+ const st: NoteState[] = [].concat(...states)
+
+ const fields = [
+ "id",
+ "status",
+ "theme",
+ "date_created",
+ "date_closed",
+ "days_open",
+ "intro",
+ "...comments"
+ ]
+ const values : string[][] = st.map(note => {
+
+
+ return [note.props.id+"",
+ note.status,
+ note.theme,
+ note.props.date_created?.substr(0, note.props.date_created.length - 3),
+ note.props.closed_at?.substr(0, note.props.closed_at.length - 3) ?? "",
+ JSON.stringify( note.intro),
+ ...note.props.comments.map(c => JSON.stringify(c.user)+": "+JSON.stringify(c.text))
+ ]
+ })
+
+ Utils.offerContentsAsDownloadableFile(
+ [fields, ...values].map(c => c.join(", ")).join("\n"),
+ "mapcomplete_import_notes_overview.csv",
+ {
+ mimetype: "text/csv"
+ }
+ )
+ })
+ }
+}
+
class MassAction extends Combine {
constructor(state: UserRelatedState, props: NoteProperties[]) {
const textField = ValidatedTextField.ForType("text").ConstructInputElement()
@@ -303,7 +344,9 @@ class ImportInspector extends VariableUiElement {
contents.push(accordeon)
const content = new Combine(contents)
return new LeftIndex(
- [new TableOfContents(content, {noTopLevel: true, maxDepth: 1}).SetClass("subtle")],
+ [new TableOfContents(content, {noTopLevel: true, maxDepth: 1}).SetClass("subtle"),
+ new DownloadStatisticsButton(perBatch)
+ ],
content
)
diff --git a/UI/ImportFlow/MapPreview.ts b/UI/ImportFlow/MapPreview.ts
index 0c329f2b9..64f8cc511 100644
--- a/UI/ImportFlow/MapPreview.ts
+++ b/UI/ImportFlow/MapPreview.ts
@@ -21,9 +21,9 @@ import {VariableUiElement} from "../Base/VariableUIElement";
import {FixedUiElement} from "../Base/FixedUiElement";
import {FlowStep} from "./FlowStep";
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
-import {AllTagsPanel} from "../SpecialVisualizations";
import Title from "../Base/Title";
import CheckBoxes from "../Input/Checkboxes";
+import {AllTagsPanel} from "../AllTagsPanel";
class PreviewPanel extends ScrollableFullScreen {
diff --git a/UI/Input/Toggle.ts b/UI/Input/Toggle.ts
index f62298601..f4f78e242 100644
--- a/UI/Input/Toggle.ts
+++ b/UI/Input/Toggle.ts
@@ -13,7 +13,7 @@ export default class Toggle extends VariableUiElement {
constructor(showEnabled: string | BaseUIElement, showDisabled: string | BaseUIElement, isEnabled: UIEventSource = new UIEventSource(false)) {
super(
- isEnabled.map(isEnabled => isEnabled ? showEnabled : showDisabled)
+ isEnabled?.map(isEnabled => isEnabled ? showEnabled : showDisabled)
);
this.isEnabled = isEnabled
}
diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts
index 5e8f21fba..15fe25b7e 100644
--- a/UI/Input/ValidatedTextField.ts
+++ b/UI/Input/ValidatedTextField.ts
@@ -250,13 +250,15 @@ class WikidataTextField extends TextFieldDef {
["subarg", "doc"],
[["removePrefixes", "remove these snippets of text from the start of the passed string to search"],
["removePostfixes", "remove these snippets of text from the end of the passed string to search"],
+ ["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",
@@ -269,11 +271,29 @@ class WikidataTextField extends TextFieldDef {
"path",
"square",
"plaza",
- ]
+ ],
+ "#": "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]
+ }]
+ }
+\`\`\`
+`
]));
}
@@ -304,9 +324,9 @@ class WikidataTextField extends TextFieldDef {
const args = inputHelperOptions.args ?? []
const searchKey = args[0] ?? "name"
- let searchFor = inputHelperOptions.feature?.properties[searchKey]?.toLowerCase()
+ let searchFor = (inputHelperOptions.feature?.properties[searchKey]?.toLowerCase() ?? "")
- const options = args[1]
+ const options: any = args[1]
if (searchFor !== undefined && options !== undefined) {
const prefixes = options["removePrefixes"]
const postfixes = options["removePostfixes"]
@@ -325,10 +345,18 @@ class WikidataTextField extends TextFieldDef {
}
}
+
+ let instanceOf : number[] = Utils.NoNull((options?.instanceOf ?? []).map(i => Wikidata.QIdToNumber(i)))
+ let notInstanceOf : number[] = Utils.NoNull((options?.notInstanceOf ?? []).map(i => Wikidata.QIdToNumber(i)))
+ console.log("Instance of", instanceOf)
+
+
return new WikidataSearchBox({
value: currentValue,
- searchText: new UIEventSource(searchFor)
+ searchText: new UIEventSource(searchFor),
+ instanceOf,
+ notInstanceOf
})
}
}
diff --git a/UI/OpeningHours/OpeningHours.ts b/UI/OpeningHours/OpeningHours.ts
index d918ed232..c4e0b04e8 100644
--- a/UI/OpeningHours/OpeningHours.ts
+++ b/UI/OpeningHours/OpeningHours.ts
@@ -1,4 +1,5 @@
import {Utils} from "../../Utils";
+import opening_hours from "opening_hours";
export interface OpeningHour {
weekday: number, // 0 is monday, 1 is tuesday, ...
@@ -458,6 +459,17 @@ export class OH {
return [changeHours, changeHourText]
}
+ public static CreateOhObject(tags: object & {_lat: number, _lon: number, _country?: string}, textToParse: string){
+ // noinspection JSPotentiallyInvalidConstructorUsage
+ return new opening_hours(textToParse, {
+ lat: tags._lat,
+ lon: tags._lon,
+ address: {
+ country_code: tags._country
+ },
+ }, {tag_key: "opening_hours"});
+ }
+
/*
Calculates when the business is opened (or on holiday) between two dates.
Returns a matrix of ranges, where [0] is a list of ranges when it is opened on monday, [1] is a list of ranges for tuesday, ...
@@ -599,6 +611,12 @@ export class OH {
}
return ohs;
}
+ public static getMondayBefore(d) {
+ d = new Date(d);
+ const day = d.getDay();
+ const diff = d.getDate() - day + (day == 0 ? -6 : 1); // adjust when day is sunday
+ return new Date(d.setDate(diff));
+ }
}
diff --git a/UI/OpeningHours/OpeningHoursVisualization.ts b/UI/OpeningHours/OpeningHoursVisualization.ts
index b3e94c7ae..f6f3a3c18 100644
--- a/UI/OpeningHours/OpeningHoursVisualization.ts
+++ b/UI/OpeningHours/OpeningHoursVisualization.ts
@@ -4,7 +4,6 @@ import {FixedUiElement} from "../Base/FixedUiElement";
import {OH} from "./OpeningHours";
import Translations from "../i18n/Translations";
import Constants from "../../Models/Constants";
-import opening_hours from "opening_hours";
import BaseUIElement from "../BaseUIElement";
import Toggle from "../Input/Toggle";
import {VariableUiElement} from "../Base/VariableUIElement";
@@ -24,7 +23,6 @@ export default class OpeningHoursVisualization extends Toggle {
]
constructor(tags: UIEventSource, state: { osmConnection?: OsmConnection }, key: string, prefix = "", postfix = "") {
- const tagsDirect = tags.data;
const ohTable = new VariableUiElement(tags
.map(tags => {
const value: string = tags[key];
@@ -41,16 +39,8 @@ export default class OpeningHoursVisualization extends Toggle {
return new FixedUiElement("No opening hours defined with key " + key).SetClass("alert")
}
try {
- // noinspection JSPotentiallyInvalidConstructorUsage
- const oh = new opening_hours(ohtext, {
- lat: tagsDirect._lat,
- lon: tagsDirect._lon,
- address: {
- country_code: tagsDirect._country
- },
- }, {tag_key: "opening_hours"});
-
- return OpeningHoursVisualization.CreateFullVisualisation(oh)
+ return OpeningHoursVisualization.CreateFullVisualisation(
+ OH.CreateOhObject(tags.data, ohtext))
} catch (e) {
console.warn(e, e.stack);
return new Combine([Translations.t.general.opening_hours.error_loading,
@@ -78,7 +68,7 @@ export default class OpeningHoursVisualization extends Toggle {
const today = new Date();
today.setHours(0, 0, 0, 0);
- const lastMonday = OpeningHoursVisualization.getMonday(today);
+ const lastMonday = OH.getMondayBefore(today);
const nextSunday = new Date(lastMonday);
nextSunday.setDate(nextSunday.getDate() + 7);
@@ -283,11 +273,5 @@ export default class OpeningHoursVisualization extends Toggle {
return Translations.t.general.opening_hours.closed_until.Subs({date: willOpenAt})
}
- private static getMonday(d) {
- d = new Date(d);
- const day = d.getDay();
- const diff = d.getDate() - day + (day == 0 ? -6 : 1); // adjust when day is sunday
- return new Date(d.setDate(diff));
- }
-
+
}
\ No newline at end of file
diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts
index 60521e273..7e32b1263 100644
--- a/UI/Popup/TagRenderingQuestion.ts
+++ b/UI/Popup/TagRenderingQuestion.ts
@@ -11,7 +11,7 @@ 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 {Translation, TypedTranslation} from "../i18n/Translation";
import Constants from "../../Models/Constants";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
@@ -51,7 +51,7 @@ export default class TagRenderingQuestion extends Combine {
const applicableMappingsSrc =
UIEventSource.ListStabilized(tags.map(tags => {
- const applicableMappings: { if: TagsFilter, icon?: string, then: any, ifnot?: TagsFilter, addExtraTags: Tag[] }[] = []
+ const applicableMappings: { if: TagsFilter, icon?: string, then: TypedTranslation, ifnot?: TagsFilter, addExtraTags: Tag[] }[] = []
for (const mapping of configuration.mappings ?? []) {
if (mapping.hideInAnswer === true) {
continue
@@ -158,7 +158,7 @@ export default class TagRenderingQuestion extends Combine {
private static GenerateInputElement(
state,
configuration: TagRenderingConfig,
- applicableMappings: { if: TagsFilter, then: any, icon?: string, ifnot?: TagsFilter, addExtraTags: Tag[] }[],
+ applicableMappings: { if: TagsFilter, then: TypedTranslation, icon?: string, ifnot?: TagsFilter, addExtraTags: Tag[] }[],
applicableUnit: Unit,
tagsSource: UIEventSource,
feedback: UIEventSource
@@ -207,7 +207,7 @@ export default class TagRenderingQuestion extends Combine {
applicableMappings.map((mapping, i) => {
return {
value: new And([mapping.if, ...allIfNotsExcept(i)]),
- shown: Translations.T(mapping.then)
+ shown: mapping.then.Subs(tagsSource.data)
}
})
)
diff --git a/UI/ShowDataLayer/ShowDataLayer.ts b/UI/ShowDataLayer/ShowDataLayer.ts
index 72ab8f909..3c25ee58b 100644
--- a/UI/ShowDataLayer/ShowDataLayer.ts
+++ b/UI/ShowDataLayer/ShowDataLayer.ts
@@ -1,50 +1,5 @@
-import {UIEventSource} from "../../Logic/UIEventSource";
-import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
-import {ShowDataLayerOptions} from "./ShowDataLayerOptions";
-import {ElementStorage} from "../../Logic/ElementStorage";
-import RenderingMultiPlexerFeatureSource from "../../Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource";
-import ScrollableFullScreen from "../Base/ScrollableFullScreen";
-/*
-// import 'leaflet-polylineoffset';
-We don't actually import it here. It is imported in the 'MinimapImplementation'-class, which'll result in a patched 'L' object.
- Even though actually importing this here would seem cleaner, we don't do this as this breaks some scripts:
- - Scripts are ran in ts-node
- - ts-node doesn't define the 'window'-object
- - Importing this will execute some code which needs the window object
-
- */
-
-/**
- * The data layer shows all the given geojson elements with the appropriate icon etc
- */
export default class ShowDataLayer {
- private static dataLayerIds = 0
- private readonly _leafletMap: UIEventSource;
- private readonly _enablePopups: boolean;
- private readonly _features: RenderingMultiPlexerFeatureSource
- private readonly _layerToShow: LayerConfig;
- private readonly _selectedElement: UIEventSource
- private readonly allElements: ElementStorage
- // Used to generate a fresh ID when needed
- private _cleanCount = 0;
- private geoLayer = undefined;
-
- /**
- * A collection of functions to call when the current geolayer is unregistered
- */
- private unregister: (() => void)[] = [];
- private isDirty = false;
- /**
- * If the selected element triggers, this is used to lookup the correct layer and to open the popup
- * Used to avoid a lot of callbacks on the selected element
- *
- * Note: the key of this dictionary is 'feature.properties.id+features.geometry.type' as one feature might have multiple presentations
- * @private
- */
- private readonly leafletLayersPerId = new Map()
- private readonly showDataLayerid: number;
- private readonly createPopup: (tags: UIEventSource, layer: LayerConfig) => ScrollableFullScreen
/**
* Creates a datalayer.
@@ -52,299 +7,10 @@ export default class ShowDataLayer {
* If 'createPopup' is set, this function is called every time that 'popupOpen' is called
* @param options
*/
- constructor(options: ShowDataLayerOptions & { layerToShow: LayerConfig }) {
- this._leafletMap = options.leafletMap;
- this.showDataLayerid = ShowDataLayer.dataLayerIds;
- ShowDataLayer.dataLayerIds++
- if (options.features === undefined) {
- console.error("Invalid ShowDataLayer invocation: options.features is undefed")
- throw "Invalid ShowDataLayer invocation: options.features is undefed"
- }
- this._features = new RenderingMultiPlexerFeatureSource(options.features, options.layerToShow);
- this._layerToShow = options.layerToShow;
- this._selectedElement = options.selectedElement
- this.allElements = options.state?.allElements;
- this.createPopup = undefined;
- this._enablePopups = options.popup !== undefined;
- if (options.popup !== undefined) {
- this.createPopup = options.popup
- }
- const self = this;
-
- options.leafletMap.addCallback(_ => {
- return self.update(options)
- }
- );
-
- this._features.features.addCallback(_ => self.update(options));
- options.doShowLayer?.addCallback(doShow => {
- const mp = options.leafletMap.data;
- if (mp === null) {
- self.Destroy()
- return true;
- }
- if (mp == undefined) {
- return;
- }
-
- if (doShow) {
- if (self.isDirty) {
- return self.update(options)
- } else {
- mp.addLayer(this.geoLayer)
- }
- } else {
- if (this.geoLayer !== undefined) {
- mp.removeLayer(this.geoLayer)
- this.unregister.forEach(f => f())
- this.unregister = []
- }
- }
-
- })
-
-
- this._selectedElement?.addCallbackAndRunD(selected => {
- self.openPopupOfSelectedElement(selected)
- })
-
- this.update(options)
-
- }
-
- private Destroy() {
- this.unregister.forEach(f => f())
- }
-
- private openPopupOfSelectedElement(selected) {
- if (selected === undefined) {
- return
- }
- if (this._leafletMap.data === undefined) {
- return;
- }
- const v = this.leafletLayersPerId.get(selected.properties.id + selected.geometry.type)
- if (v === undefined) {
- return;
- }
- const leafletLayer = v.leafletlayer
- const feature = v.feature
- if (leafletLayer.getPopup().isOpen()) {
- return;
- }
- if (selected.properties.id !== feature.properties.id) {
- return;
- }
-
- if (feature.id !== feature.properties.id) {
- // Probably a feature which has renamed
- // the feature might have as id 'node/-1' and as 'feature.properties.id' = 'the newly assigned id'. That is no good too
- console.log("Not opening the popup for", feature, "as probably renamed")
- return;
- }
- if (selected.geometry.type === feature.geometry.type // If a feature is rendered both as way and as point, opening one popup might trigger the other to open, which might trigger the one to open again
- ) {
- leafletLayer.openPopup()
- }
- }
-
- private update(options: ShowDataLayerOptions): boolean {
- if (this._features.features.data === undefined) {
- return;
- }
- this.isDirty = true;
- if (options?.doShowLayer?.data === false) {
- return;
- }
- const mp = options.leafletMap.data;
-
- if (mp === null) {
- return true; // Unregister as the map has been destroyed
- }
- if (mp === undefined) {
- return;
- }
-
- this._cleanCount++
- // clean all the old stuff away, if any
- if (this.geoLayer !== undefined) {
- mp.removeLayer(this.geoLayer);
- }
-
- const self = this;
- const data = {
- type: "FeatureCollection",
- features: []
- }
- // @ts-ignore
- this.geoLayer = L.geoJSON(data, {
- style: feature => self.createStyleFor(feature),
- pointToLayer: (feature, latLng) => self.pointToLayer(feature, latLng),
- onEachFeature: (feature, leafletLayer) => self.postProcessFeature(feature, leafletLayer)
- });
-
- const selfLayer = this.geoLayer;
- const allFeats = this._features.features.data;
- for (const feat of allFeats) {
- if (feat === undefined) {
- continue
- }
- try {
- if (feat.geometry.type === "LineString") {
- const coords = L.GeoJSON.coordsToLatLngs(feat.geometry.coordinates)
- const tagsSource = this.allElements?.addOrGetElement(feat) ?? new UIEventSource(feat.properties);
- let offsettedLine;
- tagsSource
- .map(tags => this._layerToShow.lineRendering[feat.lineRenderingIndex].GenerateLeafletStyle(tags), [], undefined, true)
- .withEqualityStabilized((a, b) => {
- if (a === b) {
- return true
- }
- if (a === undefined || b === undefined) {
- return false
- }
- return a.offset === b.offset && a.color === b.color && a.weight === b.weight && a.dashArray === b.dashArray
- })
- .addCallbackAndRunD(lineStyle => {
- if (offsettedLine !== undefined) {
- self.geoLayer.removeLayer(offsettedLine)
- }
- // @ts-ignore
- offsettedLine = L.polyline(coords, lineStyle);
- this.postProcessFeature(feat, offsettedLine)
- offsettedLine.addTo(this.geoLayer)
-
- // If 'self.geoLayer' is not the same as the layer the feature is added to, we can safely remove this callback
- return self.geoLayer !== selfLayer
- })
- } else {
- this.geoLayer.addData(feat);
- }
- } catch (e) {
- console.error("Could not add ", feat, "to the geojson layer in leaflet due to", e, e.stack)
- }
- }
-
- if (options.zoomToFeatures ?? false) {
- if (this.geoLayer.getLayers().length > 0) {
- try {
- const bounds = this.geoLayer.getBounds()
- mp.fitBounds(bounds, {animate: false})
- } catch (e) {
- console.debug("Invalid bounds", e)
- }
- }
- }
-
- if (options.doShowLayer?.data ?? true) {
- mp.addLayer(this.geoLayer)
- }
- this.isDirty = false;
- this.openPopupOfSelectedElement(this._selectedElement?.data)
- }
-
-
- private createStyleFor(feature) {
- const tagsSource = this.allElements?.addOrGetElement(feature) ?? new UIEventSource(feature.properties);
- // Every object is tied to exactly one layer
- const layer = this._layerToShow
-
- const pointRenderingIndex = feature.pointRenderingIndex
- const lineRenderingIndex = feature.lineRenderingIndex
-
- if (pointRenderingIndex !== undefined) {
- const style = layer.mapRendering[pointRenderingIndex].GenerateLeafletStyle(tagsSource, this._enablePopups)
- return {
- icon: style
- }
- }
- if (lineRenderingIndex !== undefined) {
- return layer.lineRendering[lineRenderingIndex].GenerateLeafletStyle(tagsSource.data)
- }
-
- throw "Neither lineRendering nor mapRendering defined for " + feature
- }
-
- private pointToLayer(feature, latLng): L.Layer {
- // Leaflet cannot handle geojson points natively
- // We have to convert them to the appropriate icon
- // Click handling is done in the next step
-
- const layer: LayerConfig = this._layerToShow
- if (layer === undefined) {
- return;
- }
- let tagSource = this.allElements?.getEventSourceById(feature.properties.id) ?? new UIEventSource(feature.properties)
- const clickable = !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0) && this._enablePopups
- let style: any = layer.mapRendering[feature.pointRenderingIndex].GenerateLeafletStyle(tagSource, clickable);
- const baseElement = style.html;
- if (!this._enablePopups) {
- baseElement.SetStyle("cursor: initial !important")
- }
- style.html = style.html.ConstructElement()
- return L.marker(latLng, {
- icon: L.divIcon(style)
- });
- }
-
- /**
- * Post processing - basically adding the popup
- * @param feature
- * @param leafletLayer
- * @private
- */
- private postProcessFeature(feature, leafletLayer: L.Layer) {
- const layer: LayerConfig = this._layerToShow
- if (layer.title === undefined || !this._enablePopups) {
- // No popup action defined -> Don't do anything
- // or probably a map in the popup - no popups needed!
- return;
- }
-
- const popup = L.popup({
- autoPan: true,
- closeOnEscapeKey: true,
- closeButton: false,
- autoPanPaddingTopLeft: [15, 15],
-
- }, leafletLayer);
-
- leafletLayer.bindPopup(popup);
-
- let infobox: ScrollableFullScreen = undefined;
- const id = `popup-${feature.properties.id}-${feature.geometry.type}-${this.showDataLayerid}-${this._cleanCount}-${feature.pointRenderingIndex ?? feature.lineRenderingIndex}-${feature.multiLineStringIndex ?? ""}`
- popup.setContent(`Popup for ${feature.properties.id} ${feature.geometry.type} ${id} is loading
`)
- const createpopup = this.createPopup;
- leafletLayer.on("popupopen", () => {
- if (infobox === undefined) {
- const tags = this.allElements?.getEventSourceById(feature.properties.id) ?? new UIEventSource(feature.properties);
- infobox = createpopup(tags, layer);
-
- infobox.isShown.addCallback(isShown => {
- if (!isShown) {
- leafletLayer.closePopup()
- }
- });
- }
- infobox.AttachTo(id)
- infobox.Activate();
- this.unregister.push(() => {
- console.log("Destroying infobox")
- infobox.Destroy();
- })
- if (this._selectedElement?.data?.properties?.id !== feature.properties.id) {
- this._selectedElement?.setData(feature)
- }
-
- });
-
-
- // Add the feature to the index to open the popup when needed
- this.leafletLayersPerId.set(feature.properties.id + feature.geometry.type, {
- feature: feature,
- leafletlayer: leafletLayer
- })
+ constructor(options) {
+
}
+
}
\ No newline at end of file
diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts
index 046bd28f6..23a678beb 100644
--- a/UI/SpecialVisualizations.ts
+++ b/UI/SpecialVisualizations.ts
@@ -24,7 +24,6 @@ import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer";
import Minimap from "./Base/Minimap";
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders";
import WikipediaBox from "./Wikipedia/WikipediaBox";
-import SimpleMetaTagger from "../Logic/SimpleMetaTagger";
import MultiApply from "./Popup/MultiApply";
import ShowDataLayer from "./ShowDataLayer/ShowDataLayer";
import {SubtleButton} from "./Base/SubtleButton";
@@ -46,6 +45,9 @@ import {LoginToggle} from "./Popup/LoginButton";
import {start} from "repl";
import {SubstitutedTranslation} from "./SubstitutedTranslation";
import {TextField} from "./Input/TextField";
+import Wikidata, {WikidataResponse} from "../Logic/Web/Wikidata";
+import {Translation} from "./i18n/Translation";
+import {AllTagsPanel} from "./AllTagsPanel";
export interface SpecialVisualization {
funcName: string,
@@ -56,45 +58,6 @@ export interface SpecialVisualization {
getLayerDependencies?: (argument: string[]) => string[]
}
-export class AllTagsPanel extends VariableUiElement {
-
- constructor(tags: UIEventSource, state?) {
-
- const calculatedTags = [].concat(
- SimpleMetaTagger.lazyTags,
- ...(state?.layoutToUse?.layers?.map(l => l.calculatedTags?.map(c => c[0]) ?? []) ?? []))
-
-
- super(tags.map(tags => {
- const parts = [];
- for (const key in tags) {
- if (!tags.hasOwnProperty(key)) {
- continue
- }
- let v = tags[key]
- if (v === "") {
- v = "empty string "
- }
- parts.push([key, v ?? "undefined "]);
- }
-
- for (const key of calculatedTags) {
- const value = tags[key]
- if (value === undefined) {
- continue
- }
- parts.push(["" + key + " ", value])
- }
-
- return new Table(
- ["key", "value"],
- parts
- )
- .SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;").SetClass("zebra-table")
- }))
- }
-}
-
class CloseNoteButton implements SpecialVisualization {
public readonly funcName = "close_note"
public readonly docs = "Button to close a note. A predifined text can be defined to close the note with. If the note is already closed, will show a small text."
@@ -159,19 +122,19 @@ class CloseNoteButton implements SpecialVisualization {
tags.ping()
})
})
-
- if((params.minZoom??"") !== "" && !isNaN(Number(params.minZoom))){
- closeButton = new Toggle(
+
+ if ((params.minZoom ?? "") !== "" && !isNaN(Number(params.minZoom))) {
+ closeButton = new Toggle(
closeButton,
params.zoomButton ?? "",
- state. locationControl.map(l => l.zoom >= Number(params.minZoom))
+ state.locationControl.map(l => l.zoom >= Number(params.minZoom))
)
}
-
+
return new LoginToggle(new Toggle(
t.isClosed.SetClass("thanks"),
closeButton,
-
+
isClosed
), t.loginToClose, state)
}
@@ -180,7 +143,7 @@ class CloseNoteButton implements SpecialVisualization {
export default class SpecialVisualizations {
- public static specialVisualizations : SpecialVisualization[] = SpecialVisualizations.init()
+ public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.init()
public static HelpMessage() {
@@ -207,28 +170,28 @@ export default class SpecialVisualizations {
));
return new Combine([
- new Combine([
-
- new Title("Special tag renderings", 1),
-
- "In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.",
- "General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_name(args):cssStyle}`. Note that you _do not_ need to use quotes around your arguments, the comma is enough to separate them. This also implies you cannot use a comma in your args",
- new Title("Using expanded syntax",4),
- `Instead of using \`{"render": {"en": "{some_special_visualisation(some_arg, some other really long message, more args)} , "nl": "{some_special_visualisation(some_arg, een boodschap in een andere taal, more args)}}, one can also write`,
- new FixedUiElement(JSON.stringify({
- render: {
- special:{
- type: "some_special_visualisation",
- "argname": "some_arg",
- "message":{
- en:"some other really long message",
- nl: "een boodschap in een andere taal"
- },
- "other_arg_name":"more args"
+ new Combine([
+
+ new Title("Special tag renderings", 1),
+
+ "In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.",
+ "General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_name(args):cssStyle}`. Note that you _do not_ need to use quotes around your arguments, the comma is enough to separate them. This also implies you cannot use a comma in your args",
+ new Title("Using expanded syntax", 4),
+ `Instead of using \`{"render": {"en": "{some_special_visualisation(some_arg, some other really long message, more args)} , "nl": "{some_special_visualisation(some_arg, een boodschap in een andere taal, more args)}}, one can also write`,
+ new FixedUiElement(JSON.stringify({
+ render: {
+ special: {
+ type: "some_special_visualisation",
+ "argname": "some_arg",
+ "message": {
+ en: "some other really long message",
+ nl: "een boodschap in een andere taal"
+ },
+ "other_arg_name": "more args"
+ }
}
- }
- })).SetClass("code")
- ]).SetClass("flex flex-col"),
+ })).SetClass("code")
+ ]).SetClass("flex flex-col"),
...helpTexts
]
).SetClass("flex flex-col");
@@ -297,6 +260,32 @@ export default class SpecialVisualizations {
)
},
+ {
+ funcName: "wikidata_label",
+ docs: "Shows the label of the corresponding wikidata-item",
+ args: [
+ {
+ name: "keyToShowWikidataFor",
+ doc: "Use the wikidata entry from this key to show the label",
+ defaultValue: "wikidata"
+ }
+ ],
+ example: "`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself",
+ constr: (_, tagsSource, args) =>
+ new VariableUiElement(
+ tagsSource.map(tags => tags[args[0]])
+ .map(wikidata => {
+ wikidata = Utils.NoEmpty(wikidata?.split(";")?.map(wd => wd.trim()) ?? [])[0]
+ const entry = Wikidata.LoadWikidataEntry(wikidata)
+ return new VariableUiElement(entry.map(e => {
+ if (e === undefined || e["success"] === undefined) {
+ return wikidata
+ }
+ const response = e["success"]
+ return Translation.fromMap(response.labels)
+ }))
+ }))
+ },
{
funcName: "minimap",
docs: "A small map showing the selected feature.",
@@ -315,6 +304,9 @@ export default class SpecialVisualizations {
example: "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`",
constr: (state, tagSource, args, _) => {
+ if(state === undefined){
+ return undefined
+ }
const keys = [...args]
keys.splice(0, 1)
const featureStore = state.allElements.ContainingFeatures
@@ -482,7 +474,7 @@ export default class SpecialVisualizations {
docs: "Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json[x][y][z], other: json[a][b][c] out of it and will return 'other' or 'json[a][b][c]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed_value)}",
example: "{live({url},{url:format},hour)} {live(https://data.mobility.brussels/bike/api/counts/?request=live&featureID=CB2105,hour:data.hour_cnt;day:data.day_cnt;year:data.year_cnt,hour)}",
args: [{
- name: "Url",
+ name: "Url",
doc: "The URL to load",
required: true
}, {
@@ -623,7 +615,7 @@ export default class SpecialVisualizations {
if (value === undefined) {
return undefined
}
- const allUnits = [].concat(...state.layoutToUse.layers.map(lyr => lyr.units))
+ const allUnits = [].concat(...(state?.layoutToUse?.layers?.map(lyr => lyr.units) ?? []))
const unit = allUnits.filter(unit => unit.isApplicableToKey(key))[0]
if (unit === undefined) {
return value;
@@ -783,7 +775,7 @@ export default class SpecialVisualizations {
const textField = new TextField(
{
placeholder: t.addCommentPlaceholder,
- inputStyle: "width: 100%; height: 6rem;",
+ inputStyle: "width: 100%; height: 6rem;",
textAreaRows: 3,
htmlType: "area"
}
@@ -846,7 +838,7 @@ export default class SpecialVisualizations {
textField,
new Combine([
stateButtons.SetClass("sm:mr-2"),
- new Toggle(addCommentButton,
+ new Toggle(addCommentButton,
new Combine([t.typeText]).SetClass("flex items-center h-full subtle"),
textField.GetValue().map(t => t !== undefined && t.length >= 1)).SetClass("sm:mr-2")
]).SetClass("sm:flex sm:justify-between sm:items-stretch")
@@ -947,7 +939,7 @@ export default class SpecialVisualizations {
]
specialVisualizations.push(new AutoApplyButton(specialVisualizations))
-
+
return specialVisualizations;
}
diff --git a/UI/SubstitutedTranslation.ts b/UI/SubstitutedTranslation.ts
index 856ff2482..8747a54c7 100644
--- a/UI/SubstitutedTranslation.ts
+++ b/UI/SubstitutedTranslation.ts
@@ -54,7 +54,7 @@ export class SubstitutedTranslation extends VariableUiElement {
}
const viz = proto.special;
try {
- return viz.func.constr(state, tagsSource, proto.special.args, DefaultGuiState.state).SetStyle(proto.special.style);
+ return viz.func.constr(state, tagsSource, proto.special.args, DefaultGuiState.state)?.SetStyle(proto.special.style);
} catch (e) {
console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e)
return new FixedUiElement(`Could not generate special rendering for ${viz.func.funcName}(${viz.args.join(", ")}) ${e}`).SetStyle("alert")
@@ -80,7 +80,7 @@ export class SubstitutedTranslation extends VariableUiElement {
}
}[] {
- for (const knownSpecial of SpecialVisualizations.specialVisualizations.concat(extraMappings)) {
+ for (const knownSpecial of extraMappings.concat(SpecialVisualizations.specialVisualizations)) {
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`);
diff --git a/UI/Wikipedia/WikidataSearchBox.ts b/UI/Wikipedia/WikidataSearchBox.ts
index c3df838cd..87afd9905 100644
--- a/UI/Wikipedia/WikidataSearchBox.ts
+++ b/UI/Wikipedia/WikidataSearchBox.ts
@@ -17,14 +17,20 @@ export default class WikidataSearchBox extends InputElement {
IsSelected: UIEventSource = new UIEventSource(false);
private readonly wikidataId: UIEventSource
private readonly searchText: UIEventSource
+ private readonly instanceOf?: number[];
+ private readonly notInstanceOf?: number[];
constructor(options?: {
searchText?: UIEventSource,
- value?: UIEventSource
+ value?: UIEventSource,
+ notInstanceOf?: number[],
+ instanceOf?: number[]
}) {
super();
this.searchText = options?.searchText
this.wikidataId = options?.value ?? new UIEventSource(undefined);
+ this.instanceOf = options?.instanceOf
+ this.notInstanceOf = options?.notInstanceOf
}
GetValue(): UIEventSource {
@@ -59,7 +65,9 @@ export default class WikidataSearchBox extends InputElement {
if (promise === undefined) {
promise = Wikidata.searchAndFetch(searchText, {
lang,
- maxCount: 5
+ maxCount: 5,
+ notInstanceOf: this.notInstanceOf,
+ instanceOf: this.instanceOf
}
)
WikidataSearchBox._searchCache.set(key, promise)
@@ -75,13 +83,15 @@ export default class WikidataSearchBox extends InputElement {
return new Combine([Translations.t.general.wikipedia.failed.Clone().SetClass("alert"), searchFailMessage.data])
}
+ if (searchField.GetValue().data.length === 0) {
+ return Translations.t.general.wikipedia.doSearch
+ }
+
if (searchResults.length === 0) {
return Translations.t.general.wikipedia.noResults.Subs({search: searchField.GetValue().data ?? ""})
}
- if (searchResults.length === 0) {
- return Translations.t.general.wikipedia.doSearch
- }
+
return new Combine(searchResults.map(wikidataresponse => {
const el = WikidataPreviewBox.WikidataResponsePreview(wikidataresponse).SetClass("rounded-xl p-1 sm:p-2 md:p-3 m-px border-2 sm:border-4 transition-colors")
diff --git a/assets/layers/climbing/climbing.json b/assets/layers/climbing/climbing.json
index 9c53a74a0..36cd68b9d 100644
--- a/assets/layers/climbing/climbing.json
+++ b/assets/layers/climbing/climbing.json
@@ -1,300 +1,375 @@
{
"id": "climbing",
- "name": {
- "nl": "Klimgelegenheden",
- "de": "Klettermöglichkeiten",
- "en": "Climbing opportunities",
- "ja": "登坂教室",
- "fr": "Opportunité d’escalade",
- "it": "Opportunità di arrampicata"
- },
- "minzoom": 10,
- "source": {
- "osmTags": {
- "and": [
- "sport=climbing",
- "climbing!~route",
- "leisure!~sports_centre",
- "climbing!=route_top",
- "climbing!=route_bottom"
- ]
- }
- },
- "title": {
- "render": {
- "en": "Climbing opportunity",
- "nl": "Klimgelegenheid",
- "de": "Klettermöglichkeit",
- "ja": "登坂教室",
- "nb_NO": "Klatremulighet",
- "fr": "Opportunité d’escalade",
- "it": "Opportunità di arrampicata"
- },
- "mappings": [
- {
- "if": "climbing=crag",
- "then": {
- "en": "Climbing crag {name} ",
- "fr": "Mur d’escalade {name} ",
- "it": "Muro da arrampicata {name} ",
- "de": "Klettergarten {name} "
- }
- },
- {
- "if": {
- "and": [
- {
- "or": [
- "climbing=area",
- "climbing=site"
- ]
- },
- "name~*"
- ]
- },
- "then": {
- "en": "Climbing area {name} ",
- "nl": "Klimsite {name} ",
- "fr": "Zone d’escalade {name} ",
- "de": "Klettergebiet {name} ",
- "it": "Area di arrampicata {name} "
- }
- },
- {
- "if": {
- "or": [
- "climbing=site",
- "climbing=area"
- ]
- },
- "then": {
- "en": "Climbing site",
- "nl": "Klimsite",
- "fr": "Site d’escalade",
- "de": "Klettergebiet",
- "it": "Sito di arrampicata",
- "ca": "Llocs d'escalada"
- }
- },
- {
- "if": "name~*",
- "then": {
- "nl": "Klimgelegenheid {name} ",
- "en": "Climbing opportunity {name} ",
- "fr": "Opportunité d’escalade {name} ",
- "de": "Klettermöglichkeit {name} ",
- "it": "Opportunità di arrampicata {name} "
- }
- }
- ]
- },
+ "title": null,
"description": {
- "nl": "Een klimgelegenheid",
- "de": "Eine Klettergelegenheit",
- "en": "A climbing opportunity",
- "ja": "登坂教室",
- "nb_NO": "En klatremulighet",
- "fr": "Opportunité d’escalade",
- "it": "Un’opportunità di arrampicata"
+ "en": "A dummy layer which contains tagrenderings, shared among the climbing layers"
+ },
+ "minzoom": 25,
+ "source": {
+ "osmTags": "sport=climbing"
},
"tagRenderings": [
- "images",
{
- "id": "minimap",
- "render": "{minimap(18, id, _contained_climbing_route_ids): height: 9rem; overflow: hidden; border-radius:3rem; }"
- },
- {
- "render": {
- "en": "Length overview {histogram(_length_hist)}",
- "fr": "Résumé de longueur {histogram(_length_hist)}",
- "de": "Längenübersicht {histogram(_length_hist)}",
- "it": "Riassunto della lunghezza {histogram(_length_hist)}"
+ "id": "website",
+ "question": {
+ "en": "Is there a (unofficial) website with more informations (e.g. topos)?",
+ "de": "Gibt es eine (inoffizielle) Website mit mehr Informationen (z.B. Topos)?",
+ "ja": "もっと情報のある(非公式の)ウェブサイトはありますか(例えば、topos)?",
+ "nl": "Is er een (onofficiële) website met meer informatie (b.v. met topos)?",
+ "ru": "Есть ли (неофициальный) веб-сайт с более подробной информацией (напр., topos)?",
+ "fr": "Existe-t’il un site avec plus d’informations (ex : topographie) ?",
+ "it": "C’è un sito web (anche non ufficiale) con qualche informazione in più (ad es. topografie)?"
},
- "condition": "_length_hist!~\\[\\]",
- "id": "Contained routes length hist"
- },
- {
- "render": {
- "en": "Grades overview {histogram(_difficulty_hist)}",
- "fr": "Résumé des difficultés {histogram(_difficulty_hist)}",
- "de": "Schwierigkeitsübersicht {histogram(_difficulty_hist)}",
- "it": "Riassunto delle difficoltà {histogram(_difficulty_hist)}"
+ "condition": {
+ "and": [
+ "leisure!~sports_centre",
+ "sport=climbing",
+ "office=",
+ "club="
+ ]
},
- "condition": "_difficulty_hist!~\\[\\]",
- "id": "Contained routes hist"
+ "render": "{url} ",
+ "freeform": {
+ "key": "url",
+ "type": "url"
+ }
},
{
+ "id": "average_length",
"render": {
- "en": "Contains {_contained_climbing_routes_count} routes {_contained_climbing_routes} ",
- "fr": "Contient {_contained_climbing_routes_count} voies {_contained_climbing_routes} ",
- "it": "Contiene {_contained_climbing_routes_count} vie {_contained_climbing_routes} ",
- "de": " Enthält {_contained_climbing_routes_count} Routen {_contained_climbing_routes} "
- },
- "condition": "_contained_climbing_routes~*",
- "id": "Contained_climbing_routes"
- },
- {
- "render": {
- "en": "{name} ",
- "nl": "{name} ",
- "de": "{name} ",
- "ca": "{name} ",
- "fr": "{name} ",
- "id": "{name} ",
- "ru": "{name} ",
- "ja": "{name} ",
- "it": "{name} "
+ "de": "Die Routen sind durchschnittlich {canonical(climbing:length)} lang",
+ "en": "The routes are {canonical(climbing:length)} long on average",
+ "nl": "De klimroutes zijn gemiddeld {canonical(climbing:length)} lang",
+ "ja": "ルートの長さは平均で{canonical(climbing:length)} です",
+ "fr": "Les voies font {canonical(climbing:length)} de long en moyenne",
+ "it": "Le vie sono lunghe mediamente {canonical(climbing:length)} "
},
"question": {
- "en": "What is the name of this climbing opportunity?",
- "nl": "Wat is de naam van dit Klimgelegenheid?",
- "de": "Wie heißt diese Klettergelegenheit?",
- "ja": "この登坂教室の名前は何ですか?",
- "fr": "Quel est le nom de ce site ?",
- "it": "Qual è il nome di questa opportunità di arrampicata?"
+ "de": "Wie lang sind die Routen (durchschnittlich) in Metern?",
+ "en": "What is the (average) length of the routes in meters?",
+ "nl": "Wat is de (gemiddelde) lengte van de klimroutes, in meter?",
+ "ja": "ルートの(平均)長さはメートル単位でいくつですか?",
+ "fr": "Quelle est la longueur moyenne des voies en mètres ?",
+ "it": "Quale è la lunghezza (media) delle vie in metri?"
},
"freeform": {
- "key": "name"
+ "key": "climbing:length",
+ "type": "pfloat"
+ }
+ },
+ {
+ "id": "min_difficulty",
+ "question": {
+ "de": "Welche Schwierigkeit hat hier die leichteste Route (französisch/belgisches System)?",
+ "en": "What is the grade of the easiest route here, according to the french classification system?",
+ "nl": "Wat is het niveau van de makkelijkste route, volgens het Franse classificatiesysteem?",
+ "ja": "ここで一番簡単なルートのレベルは、フランスのランク評価システムで何ですか?",
+ "fr": "Quel est le niveau de la voie la plus simple selon la classification franco-belge ?",
+ "it": "Qual è il livello della via più facile qua, secondo il sistema di classificazione francese?"
+ },
+ "render": {
+ "de": "Die leichteste Route hat hier die Schwierigkeit {climbing:grade:french:min} (französisch/belgisches System)",
+ "en": "The lowest grade is {climbing:grade:french:min} according to the french/belgian system",
+ "nl": "De minimale klimmoeilijkheid is {climbing:grade:french:min} volgens het Franse/Belgische systeem",
+ "ja": "フランス/ベルギーのランク評価システムでは、最小の難易度は{climbing:grade:french:min}です",
+ "fr": "La difficulté minimale est {climbing:grade:french:min} selon la classification franco-belge",
+ "it": "Il minimo livello di difficoltà è {climbing:grade:french:min} secondo il sistema francese/belga"
+ },
+ "freeform": {
+ "key": "climbing:grade:french:min"
+ }
+ },
+ {
+ "id": "max_difficulty",
+ "question": {
+ "de": "Welche Schwierigkeit hat hier die schwerste Route (französisch/belgisches System)?",
+ "en": "What is the highest grade route here, according to the french classification system?",
+ "nl": "Wat is het niveau van de moeilijkste route, volgens het Franse classificatiesysteem?",
+ "ja": "フランスのランク評価によると、ここで一番難しいルートのレベルはどれくらいですか?",
+ "fr": "Quel est le niveau de la voie la plus difficile selon la classification franco-belge ?",
+ "it": "Qual è il livello della via più difficile qua, secondo il sistema di classificazione francese?"
+ },
+ "render": {
+ "de": "Die schwierigste Route hat hier die Schwierigkeitsstufe {climbing:grade:french:max} (französisch/belgisches System)",
+ "en": "The highest grade is {climbing:grade:french:max} according to the french/belgian system",
+ "nl": "De maximale klimmoeilijkheid is {climbing:grade:french:max} volgens het Franse/Belgische systeem",
+ "ja": "フランス/ベルギーのランク評価システムでは、最大の難易度は{climbing:grade:french:max}です",
+ "fr": "La difficulté maximale est {climbing:grade:french:max} selon la classification franco-belge",
+ "it": "Il massimo livello di difficoltà è {climbing:grade:french:max} secondo il sistema francese/belga"
+ },
+ "freeform": {
+ "key": "climbing:grade:french:max"
+ },
+ "condition": {
+ "and": [
+ "climbing!~route",
+ "office=",
+ "club=",
+ {
+ "or": [
+ "climbing:sport=yes",
+ "sport=climbing"
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "id": "bouldering",
+ "question": {
+ "de": "Kann hier gebouldert werden?",
+ "en": "Is bouldering possible here?",
+ "nl": "Is het mogelijk om hier te bolderen?",
+ "ja": "ここでボルダリングはできますか?",
+ "nb_NO": "Er buldring mulig her?",
+ "fr": "L’escalade de bloc est-elle possible ici ?",
+ "it": "È possibile praticare ‘bouldering’ qua?"
},
"mappings": [
+ {
+ "if": "climbing:boulder=yes",
+ "then": {
+ "de": "Hier kann gebouldert werden",
+ "en": "Bouldering is possible here",
+ "nl": "Bolderen kan hier",
+ "ja": "ボルダリングはここで可能です",
+ "nb_NO": "Buldring er mulig her",
+ "fr": "L’escalade de bloc est possible",
+ "it": "L’arrampicata su massi è possibile qua"
+ }
+ },
+ {
+ "if": "climbing:boulder=no",
+ "then": {
+ "de": "Hier kann nicht gebouldert werden",
+ "en": "Bouldering is not possible here",
+ "nl": "Bolderen kan hier niet",
+ "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"
+ }
+ },
+ {
+ "if": "climbing:boulder=limited",
+ "then": {
+ "de": "Bouldern ist hier nur an wenigen Routen möglich",
+ "en": "Bouldering is possible, allthough there are only a few routes",
+ "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"
+ }
+ },
+ {
+ "if": "climbing:boulder~*",
+ "then": {
+ "de": "Hier gibt es {climbing:boulder} Boulder-Routen",
+ "en": "There are {climbing:boulder} boulder routes",
+ "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"
+ },
+ "hideInAnswer": true
+ }
+ ]
+ },
+ {
+ "id": "toprope",
+ "question": {
+ "de": "Ist Toprope-Klettern hier möglich?",
+ "en": "Is toprope climbing possible here?",
+ "nl": "Is het mogelijk om hier te toprope-klimmen?",
+ "ja": "ここでtoprope登坂はできますか?",
+ "fr": "Est-il possible d’escalader à la moulinette ?",
+ "it": "È possibile arrampicarsi con la corda dall’alto qua?"
+ },
+ "mappings": [
+ {
+ "if": "climbing:toprope=yes",
+ "then": {
+ "de": "Toprope-Klettern ist hier möglich",
+ "en": "Toprope climbing is possible here",
+ "nl": "Toprope-klimmen kan hier",
+ "ja": "ここでToprope登坂ができます",
+ "fr": "L’escalade à la moulinette est possible",
+ "it": "È possibile arrampicarsi con moulinette qua"
+ }
+ },
+ {
+ "if": "climbing:toprope=no",
+ "then": {
+ "de": "Toprope-Climbing ist hier nicht möglich",
+ "en": "Toprope climbing is not possible here",
+ "nl": "Toprope-klimmen kan hier niet",
+ "ja": "ここではToprope登坂はできません",
+ "fr": "L’escalade à la moulinette n’est pas possible",
+ "it": "Non è possibile arrampicarsi con moulinette qua"
+ }
+ },
+ {
+ "if": "climbing:toprope~*",
+ "then": {
+ "de": "Hier gibt es {climbing:toprope} Toprope-Routen",
+ "en": "There are {climbing:toprope} toprope routes",
+ "nl": "Er zijn hier {climbing:toprope} toprope routes",
+ "ja": "{climbing:toprope} 登坂ルートがある",
+ "fr": "{climbing:toprope} voies sont équipées de moulinettes",
+ "it": "Sono presenti {climbing:toprope} vie con moulinette"
+ },
+ "hideInAnswer": true
+ }
+ ]
+ },
+ {
+ "id": "sportclimbing",
+ "question": {
+ "de": "Ist hier Sportklettern möglich (feste Ankerpunkte)?",
+ "en": "Is sport climbing possible here on fixed anchors?",
+ "nl": "Is het mogelijk om hier te sportklimmen/voorklimmen op reeds aangebrachte haken?",
+ "ja": "ここでは固定アンカー式のスポーツクライミングはできますか?",
+ "it": "È possibile arrampicarsi qua con ancoraggi fissi?"
+ },
+ "mappings": [
+ {
+ "if": "climbing:sport=yes",
+ "then": {
+ "de": "Sportklettern ist hier möglich",
+ "en": "Sport climbing is possible here",
+ "nl": "Sportklimmen/voorklimmen kan hier",
+ "ru": "Здесь можно заняться спортивным скалолазанием",
+ "ja": "ここでスポーツクライミングができます",
+ "it": "L’arrampicata sportiva è possibile qua",
+ "hu": "Itt lehetőség van sportmászásra",
+ "fr": "De l’escalade est possible ici"
+ }
+ },
+ {
+ "if": "climbing:sport=no",
+ "then": {
+ "de": "Sportklettern ist hier nicht möglich",
+ "en": "Sport climbing is not possible here",
+ "nl": "Sportklimmen/voorklimmen kan hier niet",
+ "ru": "Спортивное скалолазание здесь невозможно",
+ "ja": "ここではスポーツクライミングはできません",
+ "it": "L’arrampicata sportiva non è possibile qua",
+ "hu": "Itt nincs lehetőség sportmászásra",
+ "fr": "L’escalade est impossible ici"
+ }
+ },
+ {
+ "if": "climbing:sport~*",
+ "then": {
+ "de": "Hier gibt es {climbing:sport} Sportkletter-Routen",
+ "en": "There are {climbing:sport} sport climbing routes",
+ "nl": "Er zijn hier {climbing:sport} sportklimroutes/voorklimroutes",
+ "ja": "スポーツクライミングの {climbing:sport} ルートがある",
+ "it": "Sono presenti {climbing:sport} vie di arrampicata sportiva"
+ },
+ "hideInAnswer": true
+ }
+ ]
+ },
+ {
+ "id": "trad_climbing",
+ "question": {
+ "de": "Ist hier traditionelles Klettern möglich (eigene Sicherung z.B. mit Klemmkleilen)?",
+ "en": "Is traditional climbing possible here (using own gear e.g. chocks)?",
+ "nl": "Is het mogelijk om hier traditioneel te klimmen? (Dit is klimmen met klemblokjes en friends) ",
+ "ja": "伝統的な登山はここで可能ですか(例えば、チョックのような独自のギアを使用して)?",
+ "it": "È possibile arrampicarsi in maniera tradizionale qua (usando attrezzi propri, ad es. dadi)?"
+ },
+ "mappings": [
+ {
+ "if": "climbing:traditional=yes",
+ "then": {
+ "de": "Traditionelles Klettern ist hier möglich",
+ "en": "Traditional climbing is possible here",
+ "nl": "Traditioneel klimmen kan hier",
+ "ja": "ここでは伝統的な登山が可能です",
+ "it": "L’arrampicata tradizionale è possibile qua"
+ }
+ },
+ {
+ "if": "climbing:traditional=no",
+ "then": {
+ "de": "Traditionelles Klettern ist hier nicht möglich",
+ "en": "Traditional climbing is not possible here",
+ "nl": "Traditioneel klimmen kan hier niet",
+ "ja": "伝統的な登山はここではできない",
+ "it": "L’arrampicata tradizionale non è possibile qua"
+ }
+ },
+ {
+ "if": "climbing:traditional~*",
+ "then": {
+ "de": "Hier gibt es {climbing:traditional} Routen für traditionelles Klettern",
+ "en": "There are {climbing:traditional} traditional climbing routes",
+ "nl": "Er zijn hier {climbing:traditional} traditionele klimroutes",
+ "ja": "{climbing:traditional} の伝統的な登山ルートがある",
+ "it": "Sono presenti {climbing:traditional} vie di arrampicata tradizionale"
+ },
+ "hideInAnswer": true
+ }
+ ]
+ },
+ {
+ "id": "max_bolts",
+ "question": {
+ "en": "How many bolts do routes in {title()} have at most?"
+ },
+ "render": {
+ "en": "The sport climbing routes here have at most {climbing:bolts:max} bolts.This is without relays and indicates how much quickdraws a climber needs
"
+ },
+ "freeform": {
+ "key": "climbing:bolts:max",
+ "type": "pnat",
+ "addExtraTag": [
+ "climbing:sport=yes"
+ ],
+ "inline": true
+ }
+ },
+ {
+ "id": "fee",
+ "question": {
+ "en": "Is a fee required to climb here?"
+ },
+ "render": {
+ "en": "A fee of {charge} should be paid for climbing here"
+ },
+ "freeform": {
+ "key": "charge",
+ "addExtraTags": [
+ "fee=yes"
+ ],
+ "inline": true
+ },
+ "mappings": [
+ {
+ "if": "fee=no",
+ "addExtraTags": [
+ "charge="
+ ],
+ "then": {
+ "en": "Climbing here is free of charge"
+ }
+ },
{
"if": {
"and": [
- "noname=yes",
- "name="
+ "fee=yes",
+ "charge="
]
},
"then": {
- "en": "This climbing opportunity doesn't have a name",
- "nl": "Dit Klimgelegenheid heeft geen naam",
- "de": "Diese Klettergelegenheit hat keinen Namen",
- "ja": "この登坂教室には名前がついていない",
- "fr": "Ce site n’a pas de nom",
- "it": "Questa opportunità di arrampicata non ha un nome"
- }
+ "en": "Paying a fee is required to climb here"
+ },
+ "hideInAnswer": "charge~*"
}
- ],
- "id": "name"
- },
- {
- "question": "What kind of climbing opportunity is this?",
- "mappings": [
- {
- "if": "climbing=boulder",
- "then": {
- "en": "A climbing boulder - a single rock or cliff with one or a few climbing routes which can be climbed safely without rope",
- "fr": "Rocher d’escalade, rocher avec une ou peu de voie permettant d’escalader sans corde",
- "de": "Ein Kletterfelsen - ein einzelner Felsen oder eine Klippe mit einer oder wenigen Kletterrouten, die ohne Seil sicher bestiegen werden können",
- "it": "Un masso per arrampicata (una singola roccia o falesia con una o poche vie di arrampicata che possono essere scalate in sicurezza senza una corda)"
- }
- },
- {
- "if": "climbing=crag",
- "then": {
- "en": "A climbing crag - a single rock or cliff with at least a few climbing routes",
- "fr": "Mur d’escalade, rocher avec plusieurs voies d’escalades",
- "it": "Un muro da arrampicata (un singolo masso o falesia con almeno qualche via per arrampicata)",
- "de": "Ein Kletterfelsen - ein einzelner Fels oder eine Klippe mit mindestens einigen Kletterrouten"
- }
- },
- {
- "if": "climbing=area",
- "then": "A climbing area with one or more climbing crags and/or boulders"
- }
- ],
- "id": "Type"
- },
- {
- "question": {
- "en": "What is the rock type here?",
- "fr": "Quel est le type de roche ?",
- "de": "Welchen Gesteinstyp gibt es hier?",
- "it": "Qual è il tipo di roccia qua?"
- },
- "render": {
- "en": "The rock type is {rock}",
- "fr": "La roche est du {rock}",
- "de": "Der Gesteinstyp ist {rock}",
- "it": "Il tipo di roccia è {rock}"
- },
- "freeform": {
- "key": "rock"
- },
- "mappings": [
- {
- "if": "rock=limestone",
- "then": {
- "en": "Limestone",
- "nl": "Kalksteen",
- "fr": "Calcaire",
- "de": "Kalkstein",
- "it": "Calcare"
- }
- }
- ],
- "condition": {
- "or": [
- "climbing=crag",
- "natural=cliff",
- "natural=bare_rock"
- ]
- },
- "id": "Rock type (crag/rock/cliff only)"
- }
- ],
- "presets": [
- {
- "tags": [
- "sport=climbing"
- ],
- "title": {
- "en": "a climbing opportunity",
- "nl": "een klimgelegenheid",
- "de": "eine klettermöglichkeit",
- "ja": "登坂教室",
- "nb_NO": "en klatremulighet",
- "fr": "une opportunité d’escalade",
- "it": "una opportunità di arrampicata"
- },
- "description": {
- "nl": "Een klimgelegenheid",
- "de": "Eine Klettergelegenheit",
- "en": "A climbing opportunity",
- "ja": "登坂教室",
- "nb_NO": "En klatremulighet",
- "fr": "Opportunité d’escalade",
- "it": "Un’opportunità di arrampicata"
- }
- }
- ],
- "calculatedTags": [
- "_contained_climbing_routes_properties=feat.overlapWith('climbing_route').map(f => f.feat.properties).map(p => {return {id: p.id, name: p.name, 'climbing:grade:french': p['climbing:grade:french'], 'climbing:length': p['climbing:length']} })",
- "_contained_climbing_routes=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').map(p => `${p.name ?? 'climbing route'} (${p['climbing:grade:french'] ?? 'unknown difficulty'} , ${p['climbing:length'] ?? 'unkown length'} meter) `).join('')",
- "_contained_climbing_route_ids=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').map(p => p.id)",
- "_difficulty_hist=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').map(p => p['climbing:grade:french'])",
- "_length_hist=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').map(p => p['climbing:length'])",
- "_contained_climbing_routes_count=JSON.parse(feat.properties._contained_climbing_routes_properties ?? '[]').length"
- ],
- "mapRendering": [
- {
- "icon": {
- "render": "./assets/themes/climbing/climbing_no_rope.svg"
- },
- "iconSize": {
- "render": "40,40,center"
- },
- "location": [
- "point",
- "centroid"
]
- },
- {
- "color": {
- "render": "#d38d5fAA"
- },
- "width": {
- "render": "8"
- }
}
- ]
+ ],
+ "mapRendering": null
}
\ No newline at end of file
diff --git a/assets/layers/climbing_area/climbing_area.json b/assets/layers/climbing_area/climbing_area.json
new file mode 100644
index 000000000..22b694ba5
--- /dev/null
+++ b/assets/layers/climbing_area/climbing_area.json
@@ -0,0 +1,308 @@
+{
+ "id": "climbing_area",
+ "name": {
+ "nl": "Klimgelegenheden",
+ "de": "Klettermöglichkeiten",
+ "en": "Climbing opportunities",
+ "ja": "登坂教室",
+ "fr": "Opportunité d’escalade",
+ "it": "Opportunità di arrampicata"
+ },
+ "description": {
+ "en": "An area where climbing is possible, e.g. a crag, site, boulder, ... Contains aggregation of routes"
+ },
+ "minzoom": 10,
+ "source": {
+ "osmTags": {
+ "and": [
+ "sport=climbing",
+ "climbing!~route",
+ "leisure!~sports_centre",
+ "climbing!=route_top",
+ "climbing!=route_bottom"
+ ]
+ }
+ },
+ "title": {
+ "render": {
+ "en": "Climbing opportunity",
+ "nl": "Klimgelegenheid",
+ "de": "Klettermöglichkeit",
+ "ja": "登坂教室",
+ "nb_NO": "Klatremulighet",
+ "fr": "Opportunité d’escalade",
+ "it": "Opportunità di arrampicata"
+ },
+ "mappings": [
+ {
+ "if": "climbing=crag",
+ "then": {
+ "en": "Climbing crag {name} ",
+ "fr": "Mur d’escalade {name} ",
+ "it": "Muro da arrampicata {name} ",
+ "de": "Klettergarten {name} "
+ }
+ },
+ {
+ "if": {
+ "and": [
+ {
+ "or": [
+ "climbing=area",
+ "climbing=site"
+ ]
+ },
+ "name~*"
+ ]
+ },
+ "then": {
+ "en": "Climbing area {name} ",
+ "nl": "Klimsite {name} ",
+ "fr": "Zone d’escalade {name} ",
+ "de": "Klettergebiet {name} ",
+ "it": "Area di arrampicata {name} "
+ }
+ },
+ {
+ "if": {
+ "or": [
+ "climbing=site",
+ "climbing=area"
+ ]
+ },
+ "then": {
+ "en": "Climbing site",
+ "nl": "Klimsite",
+ "fr": "Site d’escalade",
+ "de": "Klettergebiet",
+ "it": "Sito di arrampicata",
+ "ca": "Llocs d'escalada"
+ }
+ },
+ {
+ "if": "name~*",
+ "then": {
+ "nl": "Klimgelegenheid {name} ",
+ "en": "Climbing opportunity {name} ",
+ "fr": "Opportunité d’escalade {name} ",
+ "de": "Klettermöglichkeit {name} ",
+ "it": "Opportunità di arrampicata {name} "
+ }
+ }
+ ]
+ },
+ "calculatedTags": [
+ "_contained_climbing_routes_properties=feat.overlapWith('climbing_route').map(f => f.feat.properties).map(p => {return {id: p.id, name: p.name, 'climbing:grade:french': p['climbing:grade:french'], 'climbing:length': p['climbing:length']} })",
+ "_contained_climbing_routes=feat.get('_contained_climbing_routes_properties')?.map(p => `${p.name ?? 'climbing route'} (${p['climbing:grade:french'] ?? 'unknown difficulty'} , ${p['climbing:length'] ?? 'unkown length'} meter) `).join('')",
+ "_contained_climbing_route_ids=feat.get('_contained_climbing_routes_properties')?.map(p => p.id)",
+ "_difficulty_hist=feat.get('_contained_climbing_routes_properties')?.map(p => p['climbing:grade:french'])?.filter(p => (p ?? null) !== null)?.sort()",
+ "_difficulty_max=feat.get('_difficulty_hist')?.at(-1)",
+ "_difficulty_min=feat.get('_difficulty_hist')?.at(0)",
+ "_length_hist=feat.get('_contained_climbing_routes_properties')?.map(p => p['climbing:length'])?.filter(p => (p ?? null) !== null)?.sort()",
+ "_length_max=feat.get('_length_hist')?.at(-1)",
+ "_length_min=feat.get('_length_hist')?.at(0)",
+ "_bolts_hist=feat.get('_contained_climbing_routes_properties')?.map(p => p['climbing:bolts'])?.filter(p => (p ?? null) !== null)?.sort()",
+ "_bolts_max=feat.get('_bolts_hist')?.at(-1)",
+ "_bolts_min=feat.get('_bolts_hist')?.at(0)",
+ "_contained_climbing_routes_count=feat.get('_contained_climbing_routes_properties')?.length"
+ ],
+ "tagRenderings": [
+ "images",
+ {
+ "id": "minimap",
+ "render": "{minimap(18, id, _contained_climbing_route_ids): height: 9rem; overflow: hidden; border-radius:3rem; }"
+ },
+ {
+ "render": {
+ "en": "Length overview {histogram(_length_hist)}",
+ "fr": "Résumé de longueur {histogram(_length_hist)}",
+ "de": "Längenübersicht {histogram(_length_hist)}",
+ "it": "Riassunto della lunghezza {histogram(_length_hist)}"
+ },
+ "condition": "_length_hist!~\\[\\]",
+ "id": "Contained routes length hist"
+ },
+ {
+ "render": {
+ "en": "Grades overview {histogram(_difficulty_hist)}",
+ "fr": "Résumé des difficultés {histogram(_difficulty_hist)}",
+ "de": "Schwierigkeitsübersicht {histogram(_difficulty_hist)}",
+ "it": "Riassunto delle difficoltà {histogram(_difficulty_hist)}"
+ },
+ "condition": "_difficulty_hist!~\\[\\]",
+ "id": "Contained routes hist"
+ },
+ {
+ "render": {
+ "en": "Contains {_contained_climbing_routes_count} routes {_contained_climbing_routes} ",
+ "fr": "Contient {_contained_climbing_routes_count} voies {_contained_climbing_routes} ",
+ "it": "Contiene {_contained_climbing_routes_count} vie {_contained_climbing_routes} ",
+ "de": " Enthält {_contained_climbing_routes_count} Routen {_contained_climbing_routes} "
+ },
+ "condition": "_contained_climbing_routes~*",
+ "id": "Contained_climbing_routes"
+ },
+ {
+ "render": {
+ "en": "{name} ",
+ "nl": "{name} ",
+ "de": "{name} ",
+ "ca": "{name} ",
+ "fr": "{name} ",
+ "id": "{name} ",
+ "ru": "{name} ",
+ "ja": "{name} ",
+ "it": "{name} "
+ },
+ "question": {
+ "en": "What is the name of this climbing opportunity?",
+ "nl": "Wat is de naam van dit Klimgelegenheid?",
+ "de": "Wie heißt diese Klettergelegenheit?",
+ "ja": "この登坂教室の名前は何ですか?",
+ "fr": "Quel est le nom de ce site ?",
+ "it": "Qual è il nome di questa opportunità di arrampicata?"
+ },
+ "freeform": {
+ "key": "name"
+ },
+ "mappings": [
+ {
+ "if": {
+ "and": [
+ "noname=yes",
+ "name="
+ ]
+ },
+ "then": {
+ "en": "This climbing opportunity doesn't have a name",
+ "nl": "Dit Klimgelegenheid heeft geen naam",
+ "de": "Diese Klettergelegenheit hat keinen Namen",
+ "ja": "この登坂教室には名前がついていない",
+ "fr": "Ce site n’a pas de nom",
+ "it": "Questa opportunità di arrampicata non ha un nome"
+ }
+ }
+ ],
+ "id": "name"
+ },
+ {
+ "question": "What kind of climbing opportunity is this?",
+ "mappings": [
+ {
+ "if": "climbing=boulder",
+ "then": {
+ "en": "A climbing boulder - a single rock or cliff with one or a few climbing routes which can be climbed safely without rope",
+ "fr": "Rocher d’escalade, rocher avec une ou peu de voie permettant d’escalader sans corde",
+ "de": "Ein Kletterfelsen - ein einzelner Felsen oder eine Klippe mit einer oder wenigen Kletterrouten, die ohne Seil sicher bestiegen werden können",
+ "it": "Un masso per arrampicata (una singola roccia o falesia con una o poche vie di arrampicata che possono essere scalate in sicurezza senza una corda)"
+ }
+ },
+ {
+ "if": "climbing=crag",
+ "then": {
+ "en": "A climbing crag - a single rock or cliff with at least a few climbing routes",
+ "fr": "Mur d’escalade, rocher avec plusieurs voies d’escalades",
+ "it": "Un muro da arrampicata (un singolo masso o falesia con almeno qualche via per arrampicata)",
+ "de": "Ein Kletterfelsen - ein einzelner Fels oder eine Klippe mit mindestens einigen Kletterrouten"
+ }
+ },
+ {
+ "if": "climbing=area",
+ "then": "A climbing area with one or more climbing crags and/or boulders"
+ }
+ ],
+ "id": "Type"
+ },
+ {
+ "question": {
+ "en": "What is the rock type here?",
+ "fr": "Quel est le type de roche ?",
+ "de": "Welchen Gesteinstyp gibt es hier?",
+ "it": "Qual è il tipo di roccia qua?"
+ },
+ "render": {
+ "en": "The rock type is {rock}",
+ "fr": "La roche est du {rock}",
+ "de": "Der Gesteinstyp ist {rock}",
+ "it": "Il tipo di roccia è {rock}"
+ },
+ "freeform": {
+ "key": "rock"
+ },
+ "mappings": [
+ {
+ "if": "rock=limestone",
+ "then": {
+ "en": "Limestone",
+ "nl": "Kalksteen",
+ "fr": "Calcaire",
+ "de": "Kalkstein",
+ "it": "Calcare"
+ }
+ }
+ ],
+ "condition": {
+ "or": [
+ "climbing=crag",
+ "natural=cliff",
+ "natural=bare_rock"
+ ]
+ },
+ "id": "Rock type (crag/rock/cliff only)"
+ },
+ {
+ "builtin": [
+ "climbing.website",
+ "climbing.fee",
+ "climbing.bouldering"
+ ]
+ }
+ ],
+ "presets": [
+ {
+ "tags": [
+ "sport=climbing"
+ ],
+ "title": {
+ "en": "a climbing opportunity",
+ "nl": "een klimgelegenheid",
+ "de": "eine klettermöglichkeit",
+ "ja": "登坂教室",
+ "nb_NO": "en klatremulighet",
+ "fr": "une opportunité d’escalade",
+ "it": "una opportunità di arrampicata"
+ },
+ "description": {
+ "nl": "Een klimgelegenheid",
+ "de": "Eine Klettergelegenheit",
+ "en": "A climbing opportunity",
+ "ja": "登坂教室",
+ "nb_NO": "En klatremulighet",
+ "fr": "Opportunité d’escalade",
+ "it": "Un’opportunità di arrampicata"
+ }
+ }
+ ],
+ "mapRendering": [
+ {
+ "icon": {
+ "render": "./assets/themes/climbing/climbing_no_rope.svg"
+ },
+ "iconSize": {
+ "render": "40,40,center"
+ },
+ "location": [
+ "point",
+ "centroid"
+ ]
+ },
+ {
+ "color": {
+ "render": "#d38d5fAA"
+ },
+ "width": {
+ "render": "8"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/assets/layers/climbing_gym/climbing_gym.json b/assets/layers/climbing_gym/climbing_gym.json
index d93037ae6..afa06676b 100644
--- a/assets/layers/climbing_gym/climbing_gym.json
+++ b/assets/layers/climbing_gym/climbing_gym.json
@@ -55,15 +55,7 @@
"images",
{
"render": {
- "en": "{name} ",
- "nl": "{name} ",
- "de": "{name} ",
- "ca": "{name} ",
- "fr": "{name} ",
- "id": "{name} ",
- "ru": "{name} ",
- "ja": "{name} ",
- "it": "{name} "
+ "*": "{name} "
},
"question": {
"en": "What is the name of this climbing gym?",
@@ -81,7 +73,65 @@
"website",
"phone",
"email",
- "opening_hours"
+ {
+ "builtin": ["climbing.fee"]
+ },
+ "opening_hours",
+ {
+ "builtin":
+ ["climbing.average_length","climbing.min_difficulty","climbing.max_difficulty",
+ "climbing.bouldering",
+ "climbing.sportclimbing"]
+ },
+ {
+ "builtin": "climbing.max_bolts",
+ "override": {
+ "condition": "climbing:sport=yes"
+ }
+ },
+ {
+ "id": "Speed climbing?",
+ "question": {
+ "de": "Gibt es hier eine Speedkletter-Wand?",
+ "en": "Is there a speed climbing wall?",
+ "nl": "Is er een snelklimmuur (speed climbing)?",
+ "ja": "スピードクライミングウォールはありますか?",
+ "it": "È presente una prete per l’arrampicata di velocità?"
+ },
+ "mappings": [
+ {
+ "if": "climbing:speed=yes",
+ "then": {
+ "de": "Hier gibt es eine Speedkletter-Wand",
+ "en": "There is a speed climbing wall",
+ "nl": "Er is een snelklimmuur voor speed climbing",
+ "ja": "スピードクライミングウォールがある",
+ "it": "È presente una parete per l’arrampicata di velocità"
+ }
+ },
+ {
+ "if": "climbing:speed=no",
+ "then": {
+ "de": "Hier gibt es keine Speedkletter-Wand",
+ "en": "There is no speed climbing wall",
+ "nl": "Er is geen snelklimmuur voor speed climbing",
+ "ja": "スピードクライミングウォールがない",
+ "it": "Non è presente una parete per l’arrampicata di velocità"
+ }
+ },
+ {
+ "if": "climbing:speed~*",
+ "then": {
+ "de": "Hier gibt es {climbing:speed} Speedkletter-Routen",
+ "en": "There are {climbing:speed} speed climbing walls",
+ "nl": "Er zijn hier {climbing:speed} snelklimmuren",
+ "ja": "{climbing:speed} のスピードクライミングウォールがある",
+ "it": "Sono presenti {climbing:speed} pareti per l’arrampicata di velocità"
+ },
+ "hideInAnswer": true
+ }
+ ]
+ }
],
"mapRendering": [
{
diff --git a/assets/layers/climbing_opportunity/climbing_opportunity.json b/assets/layers/climbing_opportunity/climbing_opportunity.json
index c9afa69f0..3a5e85106 100644
--- a/assets/layers/climbing_opportunity/climbing_opportunity.json
+++ b/assets/layers/climbing_opportunity/climbing_opportunity.json
@@ -9,6 +9,9 @@
"fr": "Opportunités d’escalade ?",
"it": "Opportunità di arrampicata?"
},
+ "description": {
+ "en": "Fallback layer with items on which climbing _might_ be possible. It is loaded when zoomed in a lot, to prevent duplicate items to be added"
+ },
"minzoom": 19,
"source": {
"osmTags": {
@@ -38,15 +41,6 @@
"it": "Opportunità di arrampicata?"
}
},
- "description": {
- "nl": "Een klimgelegenheid?",
- "de": "Eine Klettergelegenheit?",
- "en": "A climbing opportunity?",
- "ja": "登坂教室?",
- "nb_NO": "En klatremulighet?",
- "fr": "Opportunité d’escalade ?",
- "it": "Un’opportunità di arrampicata?"
- },
"tagRenderings": [
{
"id": "climbing-opportunity-name",
diff --git a/assets/layers/climbing_route/climbing_route.json b/assets/layers/climbing_route/climbing_route.json
index 7b5c8f6cf..43e005d7d 100644
--- a/assets/layers/climbing_route/climbing_route.json
+++ b/assets/layers/climbing_route/climbing_route.json
@@ -9,6 +9,9 @@
"fr": "Voies d’escalade",
"it": "Vie di arrampicata"
},
+ "description": {
+ "en": "A single climbing route and its properties. Some properties are derived from the containing features"
+ },
"minzoom": 18,
"source": {
"osmTags": {
@@ -141,6 +144,7 @@
"id": "Difficulty"
},
{
+ "id": "bolts",
"question": {
"en": "How many bolts does this route have before reaching the anchor?",
"fr": "Combien de prises cette voie possède avant d’atteindre la moulinette ?",
@@ -148,7 +152,7 @@
"it": "Quanti bulloni sono presenti in questo percorso prima di arrivare alla moulinette?"
},
"render": {
- "en": "This route has {climbing:bolts} bolts",
+ "en": "This route has {climbing:bolts} bolts This is without relays and indicates how much quickdraws a climber needs
",
"fr": "Cette voie a {climbing:bolts} prises",
"de": "Diese Kletterroute hat {climbing:bolts} Haken",
"it": "Questo percorso ha {climbing:bolts} bulloni"
@@ -158,7 +162,8 @@
"type": "pnat",
"addExtraTag": [
"climbing:bolted=yes"
- ]
+ ],
+ "inline": true
},
"mappings": [
{
@@ -169,29 +174,15 @@
"de": "Auf dieser Kletterroute sind keine Haken vorhanden",
"it": "In questo percorso non sono presenti bulloni"
},
- "hideInAnswer": true
- },
- {
- "if": "climbing:bolted=no&climbing:bolts=",
- "then": {
- "en": "This route is not bolted",
- "fr": "Cette voie n’a pas de prises",
- "de": "Auf dieser Kletterroute sind keine Haken vorhanden",
- "it": "In questo percorso non sono presenti bulloni"
- }
+ "addExtraTags": [
+ "climbing:bolts="
+ ]
}
- ],
- "id": "Bolts"
- },
- {
- "question": "Is there other relevant info?",
- "render": "Description {description}",
- "freeform": {
- "key": "description"
- },
- "id": "Description"
+ ]
},
+ "description",
{
+ "id": "Rock type via embedded feature",
"render": {
"en": "The rock type is {_embedding_features_with_rock:rock} as stated on the surrounding crag ",
"fr": "Le type de roche est {_embedding_features_with_rock:rock} selon le mur ",
@@ -200,8 +191,7 @@
},
"freeform": {
"key": "_embedding_features_with_rock:rock"
- },
- "id": "Rock type"
+ }
}
],
"presets": [
@@ -233,9 +223,18 @@
],
"label": {
"mappings": [
+ {
+ "if": {
+ "and": [
+ "climbing:grade:french~*",
+ "name~*"
+ ]
+ },
+ "then": "{name} {climbing:grade:french}
"
+ },
{
"if": "name~*",
- "then": "{name}
"
+ "then": "{name}
"
}
]
}
diff --git a/assets/layers/etymology/etymology.json b/assets/layers/etymology/etymology.json
index 0bdef7c3b..7b9dc5b1f 100644
--- a/assets/layers/etymology/etymology.json
+++ b/assets/layers/etymology/etymology.json
@@ -52,6 +52,10 @@
"helperArgs": [
"name",
{
+ "notInstanceOf": [
+ "Q79007",
+ "Q22698"
+ ],
"removePostfixes": [
"steenweg",
"heirbaan",
@@ -70,7 +74,9 @@
"wegel",
"kerk",
"church",
- "kaai"
+ "kaai",
+ "park",
+ "parque"
]
}
]
diff --git a/assets/layers/tree_node/tree_node.json b/assets/layers/tree_node/tree_node.json
index 4ab109afd..ce12eb676 100644
--- a/assets/layers/tree_node/tree_node.json
+++ b/assets/layers/tree_node/tree_node.json
@@ -31,6 +31,28 @@
"es": "Árbol"
},
"mappings": [
+ {
+ "if": {
+ "and": ["name~*","species:wikidata~*"]
+ },
+ "then": {
+ "*": "{name} ({wikidata_label(species:wikidata)})"
+ }
+ },
+ {
+ "if": {
+ "and": ["name~*"]
+ },
+ "then": {
+ "*": "{name}"
+ }
+ },
+ {
+ "if": "species:wikidata~*",
+ "then": {
+ "*": "{wikidata_label(species:wikidata)}"
+ }
+ },
{
"if": "species~*",
"then": {
@@ -334,6 +356,41 @@
]
}
},
+ {
+ "id": "tree-species-wikidata",
+ "question": {
+ "en": "What species is this tree?"
+ },
+ "render": {
+ "*": "{wikipedia(species:wikidata):max-height: 25rem}"
+ },
+ "freeform": {
+ "key": "species:wikidata",
+ "type": "wikidata",
+ "helperArgs": [
+ "species",
+ {
+ "instanceOf": [
+ 10884,
+ 16521
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "id": "tree-wikipedia",
+ "#": "If this tree has a wikipedia article, show it. People can _only_ set the species though!",
+ "render": {
+ "*": "{wikipedia()}"
+ },
+ "condition": {
+ "or": [
+ "wikipedia~*",
+ "wikidata~*"
+ ]
+ }
+ },
{
"render": {
"nl": "Naam: {name}",
diff --git a/assets/layers/waste_basket/waste_basket.json b/assets/layers/waste_basket/waste_basket.json
index f0cda1205..67d968702 100644
--- a/assets/layers/waste_basket/waste_basket.json
+++ b/assets/layers/waste_basket/waste_basket.json
@@ -31,6 +31,7 @@
"de": "Dies ist ein öffentlicher Abfalleimer, in den Sie Ihren Müll entsorgen können."
},
"tagRenderings": [
+ "images",
{
"id": "waste-basket-waste-types",
"question": {
diff --git a/assets/themes/climbing/climbing.css b/assets/themes/climbing/climbing.css
new file mode 100644
index 000000000..3acbe9041
--- /dev/null
+++ b/assets/themes/climbing/climbing.css
@@ -0,0 +1,48 @@
+/* a few extra colours, mostly colours difficulties. Debuggable in css-test.html */
+
+.climbing- {
+ /*Fallback in case of unrecognized difficulty*/
+ background: white;
+ border: 1px solid black;
+
+ background: repeating-linear-gradient( -45deg, #a7b9ae, #a7b9ae 10px, #f7f7f7 10px, #f7f7f7 20px );
+
+}
+
+.climbing-2 {
+ background: #a2ff00;
+}
+
+.climbing-3 {
+ background: yellow;
+}
+
+.climbing-4 {
+ background: orange;
+}
+
+.climbing-5 {
+ background: blue;
+ color: white;
+}
+
+.climbing-6 {
+ background: red;
+ color: white;
+}
+
+.climbing-7 {
+ background: #ef47ec;
+}
+
+.climbing-8 {
+ background: black;
+ color: white;
+}
+
+.climbing-9 {
+ background: white;
+ color: black;
+ border: 1px solid black;
+}
+
diff --git a/assets/themes/climbing/climbing.json b/assets/themes/climbing/climbing.json
index 7c305dda4..66a53b182 100644
--- a/assets/themes/climbing/climbing.json
+++ b/assets/themes/climbing/climbing.json
@@ -42,699 +42,234 @@
"startLon": 0,
"startZoom": 1,
"widenFactor": 1.5,
+ "customCss": "./assets/themes/climbing/climbing.css",
"layers": [
- "climbing_club",
- "climbing_gym",
- "climbing_route",
- "climbing",
- "climbing_opportunity"
- ],
- "overrideAll": {
- "allowMove": {
- "enableRelocation": false,
- "enableImproveAccuracy": true
- },
- "+titleIcons": [
- {
- "render": "{climbing:length}m
",
- "condition": "climbing:length~*"
- },
- {
- "mappings": [
+ {
+ "builtin": [
+ "climbing_club",
+ "climbing_gym",
+ "climbing_route",
+ "climbing_area",
+ "climbing_opportunity"
+ ],
+ "override": {
+ "allowMove": {
+ "enableRelocation": false,
+ "enableImproveAccuracy": true
+ },
+ "+titleIcons": [
{
- "if": "climbing:bolts~*",
- "then": "{climbing:bolts}
"
+ "render": "{climbing:length}m
",
+ "condition": "climbing:length~*"
},
{
- "if": "climbing:bolted=yes",
- "then": " "
+ "mappings": [
+ {
+ "if": "__bolts_max~*",
+ "then": "{__bolts_max}
"
+ },
+ {
+ "if": "climbing:bolted=yes",
+ "then": " "
+ }
+ ]
+ },
+ {
+ "id": "Min difficulty",
+ "condition": "__difficulty_min~*",
+ "render": " {__difficulty_min}
"
+ },
+ {
+ "id": "max difficulty",
+ "condition": "__difficulty_max~*",
+ "render": " {__difficulty_max}
"
+ },
+ {
+ "render": " {climbing:grade:french}
"
}
- ]
- },
- {
- "mappings": [
- {
- "if": "climbing:grade:french~3.*",
- "then": " {climbing:grade:french}
"
- },
- {
- "if": "climbing:grade:french~4.*",
- "then": " {climbing:grade:french}
"
- },
- {
- "if": "climbing:grade:french~5.*",
- "then": " {climbing:grade:french}
"
- },
- {
- "if": "climbing:grade:french~6.*",
- "then": " {climbing:grade:french}
"
- },
- {
- "if": "climbing:grade:french~7.*",
- "then": " {climbing:grade:french}
"
- },
- {
- "if": "climbing:grade:french~*",
- "then": " {climbing:grade:french}
"
- }
- ]
- }
- ],
- "+calculatedTags": [
- "_embedding_feature_properties=feat.overlapWith('climbing').map(f => f.feat.properties).filter(p => p !== undefined).map(p => {return{access: p.access, id: p.id, name: p.name, climbing: p.climbing, 'access:description': p['access:description']}})",
- "_embedding_features_with_access=JSON.parse(feat.properties._embedding_feature_properties ?? '[]').filter(p => p.access !== undefined)[0]",
- "_embedding_feature_with_rock=JSON.parse(feat.properties._embedding_feature_properties ?? '[]').filter(p => p.rock !== undefined)[0] ?? '{}'",
- "_embedding_features_with_rock:rock=JSON.parse(feat.properties._embedding_feature_with_rock ?? '{}')?.rock",
- "_embedding_features_with_rock:id=JSON.parse(feat.properties._embedding_feature_with_rock ?? '{}')?.id",
- "_embedding_feature:access=JSON.parse(feat.properties._embedding_features_with_access ?? '{}').access",
- "_embedding_feature:access:description=JSON.parse(feat.properties._embedding_features_with_access ?? '{}')['access:description']",
- "_embedding_feature:id=JSON.parse(feat.properties._embedding_features_with_access ?? '{}').id"
- ],
- "units+": [
- {
- "appliesToKey": [
- "climbing:length",
- "climbing:length:min",
- "climbing:length:max"
],
- "applicableUnits": [
+ "+calculatedTags": [
+ "_embedding_feature_properties=feat.overlapWith('climbing_area').map(f => f.feat.properties).filter(p => p !== undefined).map(p => {return{access: p.access, id: p.id, name: p.name, climbing: p.climbing, 'access:description': p['access:description']}})",
+ "_embedding_features_with_access=feat.get('_embedding_feature_properties')?.filter(p => p.access !== undefined)?.at(0)",
+ "_embedding_feature_with_rock=feat.get('_embedding_feature_properties')?.filter(p => p.rock !== undefined)?.at(0)",
+ "_embedding_features_with_rock:rock=feat.get('_embedding_feature_with_rock')?.rock",
+ "_embedding_features_with_rock:id=feat.get('_embedding_feature_with_rock')?.id",
+ "_embedding_feature:access=feat.get('_embedding_features_with_access')?.access",
+ "_embedding_feature:access:description=(feat.get('_embedding_features_with_access')??{})['access:description']",
+ "_embedding_feature:id=feat.get('_embedding_features_with_access')?.id",
+ "__difficulty_max= feat.properties['climbing:grade:french:max'] ?? feat.properties['_difficulty_max']",
+ "__difficulty_min= feat.properties['climbing:grade:french:min'] ?? feat.properties['_difficulty_min']",
+ "__difficulty_max:char= feat.properties['__difficulty_max']?.at(0)",
+ "__difficulty_min:char= feat.properties['__difficulty_min']?.at(0)",
+ "__difficulty:char= feat.properties['climbing:grade:french']?.at(0)",
+ "__bolts_max= feat.get('climbing:bolts:max') ?? feat.get('climbing:bolts') ?? feat.get('_bolts_max')"
+ ],
+ "units+": [
{
- "canonicalDenomination": "",
- "alternativeDenomination": [
- "m",
- "meter",
- "meters"
+ "appliesToKey": [
+ "climbing:length",
+ "climbing:length:min",
+ "climbing:length:max"
],
- "human": {
- "en": " meter",
- "nl": " meter",
- "fr": " mètres",
- "de": " Meter",
- "eo": " metro",
- "it": " metri",
- "ru": " метр",
- "ca": " metre"
- },
- "default": true
- },
+ "applicableUnits": [
+ {
+ "canonicalDenomination": "",
+ "alternativeDenomination": [
+ "m",
+ "meter",
+ "meters"
+ ],
+ "human": {
+ "en": " meter",
+ "nl": " meter",
+ "fr": " mètres",
+ "de": " Meter",
+ "eo": " metro",
+ "it": " metri",
+ "ru": " метр",
+ "ca": " metre"
+ },
+ "default": true
+ },
+ {
+ "canonicalDenomination": "ft",
+ "alternativeDenomination": [
+ "feet",
+ "voet"
+ ],
+ "human": {
+ "en": " feet",
+ "nl": " voet",
+ "fr": " pieds",
+ "de": " Fuß",
+ "eo": " futo",
+ "it": " piedi",
+ "ca": " peus"
+ }
+ }
+ ]
+ }
+ ],
+ "tagRenderings+": [
{
- "canonicalDenomination": "ft",
- "alternativeDenomination": [
- "feet",
- "voet"
+ "id": "Access from containing feature",
+ "mappings": [
+ {
+ "if": "_embedding_feature:access=yes",
+ "then": {
+ "en": "The containing feature states that this is publicly accessible {_embedding_feature:access:description}",
+ "nl": "Een omvattend element geeft aan dat dit publiek toegangkelijk is {_embedding_feature:access:description}",
+ "fr": "L’élément englobant indique un accès libre {_embedding_feature:access:description}",
+ "it": "L’ elemento in cui è contenuto indica che è pubblicamente accessibile {_embedding_feature:access:description}",
+ "de": "Das enthaltende Objekt gibt an, dass es öffentlich zugänglich ist {_embedding_feature:access:description}"
+ }
+ },
+ {
+ "if": "_embedding_feature:access=permit",
+ "then": {
+ "en": "The containing feature states that a permit is needed to access {_embedding_feature:access:description}",
+ "nl": "Een omvattend element geeft aan dat een toelating nodig is om hier te klimmen {_embedding_feature:access:description}",
+ "fr": "L’élément englobant indique qu’ une autorisation d’accès est nécessaire {_embedding_feature:access:description}",
+ "it": "L’elemento che lo contiene indica che è richiesto un’autorizzazione per accedervi {_embedding_feature:access:description}",
+ "de": "Das enthaltende Objekt besagt, dass eine Genehmigung erforderlich ist für den Zugang zu {_embedding_feature:access:description}"
+ }
+ },
+ {
+ "if": "_embedding_feature:access=customers",
+ "then": {
+ "en": "The containing feature states that this is only accessible to customers {_embedding_feature:access:description}",
+ "fr": "L’élément englobant indique que l’accès est réservés aux clients {_embedding_feature:access:description}",
+ "it": "L’ elemento che lo contiene indica che è accessibile solo ai clienti {_embedding_feature:access:description}",
+ "de": "Das enthaltende Objekt besagt, dass es nur für Kunden zugänglich ist {_embedding_feature:access:description}"
+ }
+ },
+ {
+ "if": "_embedding_feature:access=members",
+ "then": {
+ "en": "The containing feature states that this is only accessible to club members {_embedding_feature:access:description}",
+ "fr": "L’élément englobant indique que l’accès est réservé aux membres {_embedding_feature:access:description}",
+ "it": "L’ elemento che lo contiene indica che è accessibile solamente ai membri del club {_embedding_feature:access:description}",
+ "de": "Das enthaltende Objekt besagt, dass es nur für Mitglieder zugänglich ist {_embedding_feature:access:description}"
+ }
+ },
+ {
+ "if": "_embedding_feature:access=no",
+ "then": "Not accessible as stated by the containing feature "
+ }
],
- "human": {
- "en": " feet",
- "nl": " voet",
- "fr": " pieds",
- "de": " Fuß",
- "eo": " futo",
- "it": " piedi",
- "ca": " peus",
- "es": " pies"
- }
- }
- ]
- }
- ],
- "tagRenderings+": [
- {
- "id": "Website",
- "question": {
- "en": "Is there a (unofficial) website with more informations (e.g. topos)?",
- "de": "Gibt es eine (inoffizielle) Website mit mehr Informationen (z.B. Topos)?",
- "ja": "もっと情報のある(非公式の)ウェブサイトはありますか(例えば、topos)?",
- "nl": "Is er een (onofficiële) website met meer informatie (b.v. met topos)?",
- "ru": "Есть ли (неофициальный) веб-сайт с более подробной информацией (напр., topos)?",
- "fr": "Existe-t’il un site avec plus d’informations (ex : topographie) ?",
- "it": "C’è un sito web (anche non ufficiale) con qualche informazione in più (ad es. topografie)?"
- },
- "condition": {
- "and": [
- "leisure!~sports_centre",
- "sport=climbing",
- "office=",
- "club="
- ]
- },
- "render": "{url} ",
- "freeform": {
- "key": "url",
- "type": "url"
- }
- },
- {
- "id": "Access from containing feature",
- "mappings": [
- {
- "if": "_embedding_feature:access=yes",
- "then": {
- "en": "The containing feature states that this is publicly accessible {_embedding_feature:access:description}",
- "nl": "Een omvattend element geeft aan dat dit publiek toegangkelijk is {_embedding_feature:access:description}",
- "fr": "L’élément englobant indique un accès libre {_embedding_feature:access:description}",
- "it": "L’ elemento in cui è contenuto indica che è pubblicamente accessibile {_embedding_feature:access:description}",
- "de": "Das enthaltende Objekt gibt an, dass es öffentlich zugänglich ist {_embedding_feature:access:description}"
- }
+ "condition": "_embedding_feature:access~*"
},
{
- "if": "_embedding_feature:access=permit",
- "then": {
- "en": "The containing feature states that a permit is needed to access {_embedding_feature:access:description}",
- "nl": "Een omvattend element geeft aan dat een toelating nodig is om hier te klimmen {_embedding_feature:access:description}",
- "fr": "L’élément englobant indique qu’ une autorisation d’accès est nécessaire {_embedding_feature:access:description}",
- "it": "L’elemento che lo contiene indica che è richiesto un’autorizzazione per accedervi {_embedding_feature:access:description}",
- "de": "Das enthaltende Objekt besagt, dass eine Genehmigung erforderlich ist für den Zugang zu {_embedding_feature:access:description}"
- }
- },
- {
- "if": "_embedding_feature:access=customers",
- "then": {
- "en": "The containing feature states that this is only accessible to customers {_embedding_feature:access:description}",
- "fr": "L’élément englobant indique que l’accès est réservés aux clients {_embedding_feature:access:description}",
- "it": "L’ elemento che lo contiene indica che è accessibile solo ai clienti {_embedding_feature:access:description}",
- "de": "Das enthaltende Objekt besagt, dass es nur für Kunden zugänglich ist {_embedding_feature:access:description}"
- }
- },
- {
- "if": "_embedding_feature:access=members",
- "then": {
- "en": "The containing feature states that this is only accessible to club members {_embedding_feature:access:description}",
- "fr": "L’élément englobant indique que l’accès est réservé aux membres {_embedding_feature:access:description}",
- "it": "L’ elemento che lo contiene indica che è accessibile solamente ai membri del club {_embedding_feature:access:description}",
- "de": "Das enthaltende Objekt besagt, dass es nur für Mitglieder zugänglich ist {_embedding_feature:access:description}"
- }
- },
- {
- "if": "_embedding_feature:access=no",
- "then": "Not accessible as stated by the containing feature "
- }
- ],
- "condition": "_embedding_feature:access~*"
- },
- {
- "id": "Access",
- "question": {
- "en": "Who can access here?",
- "fr": "Qui peut y accéder ?",
- "de": "Wer hat hier Zugang?",
- "it": "Chi può accedervi?",
- "es": "¿Quién pueden acceder aquí?"
- },
- "mappings": [
- {
- "if": "access=yes",
- "then": {
- "en": "Publicly accessible to anyone",
- "fr": "Libre d’accès",
- "de": "Öffentlich zugänglich für jedermann",
- "it": "Pubblicamente accessibile a chiunque",
- "es": "Accesible públicamente a cualquiera"
- }
- },
- {
- "if": "access=permit",
- "then": {
- "en": "You need a permit to access here",
- "fr": "Une autorisation est nécessaire",
- "de": "Zugang nur mit Genehmigung",
- "it": "È necessario avere un’autorizzazione per entrare",
- "es": "Necesitas una autorización para acceder aquí"
- }
- },
- {
- "if": "access=customers",
- "then": {
- "en": "Only customers",
- "fr": "Réservé aux clients",
- "de": "Nur für Kunden",
- "it": "Riservato ai clienti",
- "ca": "Només clients",
- "es": "Solo clientes"
- }
- },
- {
- "if": "access=members",
- "then": {
- "en": "Only club members",
- "ru": "Только членам клуба",
- "fr": "Réservé aux membres",
- "de": "Nur für Vereinsmitglieder",
- "it": "Riservato ai membri del club",
- "ca": "Només membres del club",
- "es": "Solo miembros del club"
- }
- },
- {
- "if": "access=no",
- "then": "Not accessible"
- }
- ],
- "condition": {
- "and": [
- "climbing!=no",
- "office=",
- "club=",
- {
- "or": [
- "sport=climbing",
- "climbing:sport=yes"
- ]
+ "id": "access",
+ "question": {
+ "en": "Who can access here?",
+ "fr": "Qui peut y accéder ?",
+ "de": "Wer hat hier Zugang?",
+ "it": "Chi può accedervi?"
},
- {
+ "mappings": [
+ {
+ "if": "access=yes",
+ "then": {
+ "en": "Publicly accessible to anyone",
+ "fr": "Libre d’accès",
+ "de": "Öffentlich zugänglich für jedermann",
+ "it": "Pubblicamente accessibile a chiunque"
+ }
+ },
+ {
+ "if": "access=permit",
+ "then": {
+ "en": "You need a permit to access here",
+ "fr": "Une autorisation est nécessaire",
+ "de": "Zugang nur mit Genehmigung",
+ "it": "È necessario avere un’autorizzazione per entrare"
+ }
+ },
+ {
+ "if": "access=customers",
+ "then": {
+ "en": "Only customers",
+ "fr": "Réservé aux clients",
+ "de": "Nur für Kunden",
+ "it": "Riservato ai clienti",
+ "ca": "Només clients"
+ }
+ },
+ {
+ "if": "access=members",
+ "then": {
+ "en": "Only club members",
+ "ru": "Только членам клуба",
+ "fr": "Réservé aux membres",
+ "de": "Nur für Vereinsmitglieder",
+ "it": "Riservato ai membri del club",
+ "ca": "Només membres del club"
+ }
+ },
+ {
+ "if": "access=no",
+ "then": "Not accessible"
+ }
+ ],
+ "condition": {
"or": [
"access~*",
"_embedding_feature:access="
]
}
- ]
- }
- },
- {
- "id": "Access description (without _embedding_feature:access:description)",
- "render": "{access:description}",
- "freeform": {
- "key": "access:description"
- }
- },
- {
- "id": "Avg length?",
- "render": {
- "de": "Die Routen sind durchschnittlich {canonical(climbing:length)} lang",
- "en": "The routes are {canonical(climbing:length)} long on average",
- "nl": "De klimroutes zijn gemiddeld {canonical(climbing:length)} lang",
- "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 rotas tienen una longitud media de {canonical(climbing:length)} "
- },
- "condition": {
- "and": [
- "climbing!~route",
- "office=",
- "club=",
- "climbing:toprope!=no",
- {
- "or": [
- "sport=climbing",
- "climbing:sport=yes",
- "climbing=traditional",
- "climbing=gym"
- ]
- }
- ]
- },
- "question": {
- "de": "Wie lang sind die Routen (durchschnittlich) in Metern?",
- "en": "What is the (average) length of the routes in meters?",
- "nl": "Wat is de (gemiddelde) lengte van de klimroutes, in meter?",
- "ja": "ルートの(平均)長さはメートル単位でいくつですか?",
- "fr": "Quelle est la longueur moyenne des voies en mètres ?",
- "it": "Quale è la lunghezza (media) delle vie in metri?",
- "es": "¿Cuál es la longitud (media) de la ruta en metros?"
- },
- "freeform": {
- "key": "climbing:length",
- "type": "pnat"
- }
- },
- {
- "id": "Difficulty-min",
- "question": {
- "de": "Welche Schwierigkeit hat hier die leichteste Route (französisch/belgisches System)?",
- "en": "What is the grade of the easiest route here, according to the french classification system?",
- "nl": "Wat is het niveau van de makkelijkste route, volgens het Franse classificatiesysteem?",
- "ja": "ここで一番簡単なルートのレベルは、フランスのランク評価システムで何ですか?",
- "fr": "Quel est le niveau de la voie la plus simple selon la classification franco-belge ?",
- "it": "Qual è il livello della via più facile qua, secondo il sistema di classificazione francese?",
- "es": "¿Cual es el nivel de la ruta más fácil aquí, de acuerdo con el sistema de clasificación francés?"
- },
- "render": {
- "de": "Die leichteste Route hat hier die Schwierigkeit {climbing:grade:french:min} (französisch/belgisches System)",
- "en": "The lowest grade is {climbing:grade:french:min} according to the french/belgian system",
- "nl": "De minimale klimmoeilijkheid is {climbing:grade:french:min} volgens het Franse/Belgische systeem",
- "ja": "フランス/ベルギーのランク評価システムでは、最小の難易度は{climbing:grade:french:min}です",
- "fr": "La difficulté minimale est {climbing:grade:french:min} selon la classification franco-belge",
- "it": "Il minimo livello di difficoltà è {climbing:grade:french:min} secondo il sistema francese/belga"
- },
- "freeform": {
- "key": "climbing:grade:french:min"
- },
- "condition": {
- "and": [
- "climbing!~route",
- "office=",
- "club=",
- {
- "or": [
- "climbing:sport=yes",
- "sport=climbing"
- ]
- }
- ]
- }
- },
- {
- "id": "Difficulty-max",
- "question": {
- "de": "Welche Schwierigkeit hat hier die schwerste Route (französisch/belgisches System)?",
- "en": "What is the highest grade route here, according to the french classification system?",
- "nl": "Wat is het niveau van de moeilijkste route, volgens het Franse classificatiesysteem?",
- "ja": "フランスのランク評価によると、ここで一番難しいルートのレベルはどれくらいですか?",
- "fr": "Quel est le niveau de la voie la plus difficile selon la classification franco-belge ?",
- "it": "Qual è il livello della via più difficile qua, secondo il sistema di classificazione francese?",
- "es": "¿Cual es la ruta de mayor nivel aquí, de acuerdo al sistema de clasificación francés?"
- },
- "render": {
- "de": "Die schwierigste Route hat hier die Schwierigkeitsstufe {climbing:grade:french:max} (französisch/belgisches System)",
- "en": "The highest grade is {climbing:grade:french:max} according to the french/belgian system",
- "nl": "De maximale klimmoeilijkheid is {climbing:grade:french:max} volgens het Franse/Belgische systeem",
- "ja": "フランス/ベルギーのランク評価システムでは、最大の難易度は{climbing:grade:french:max}です",
- "fr": "La difficulté maximale est {climbing:grade:french:max} selon la classification franco-belge",
- "it": "Il massimo livello di difficoltà è {climbing:grade:french:max} secondo il sistema francese/belga"
- },
- "freeform": {
- "key": "climbing:grade:french:max"
- },
- "condition": {
- "and": [
- "climbing!~route",
- "office=",
- "club=",
- {
- "or": [
- "climbing:sport=yes",
- "sport=climbing"
- ]
- }
- ]
- }
- },
- {
- "id": "Boldering?",
- "question": {
- "de": "Kann hier gebouldert werden?",
- "en": "Is bouldering possible here?",
- "nl": "Is het mogelijk om hier te bolderen?",
- "ja": "ここでボルダリングはできますか?",
- "nb_NO": "Er buldring mulig her?",
- "fr": "L’escalade de bloc est-elle possible ici ?",
- "it": "È possibile praticare ‘bouldering’ qua?"
- },
- "mappings": [
- {
- "if": "climbing:boulder=yes",
- "then": {
- "de": "Hier kann gebouldert werden",
- "en": "Bouldering is possible here",
- "nl": "Bolderen kan hier",
- "ja": "ボルダリングはここで可能です",
- "nb_NO": "Buldring er mulig her",
- "fr": "L’escalade de bloc est possible",
- "it": "L’arrampicata su massi è possibile qua"
- }
},
{
- "if": "climbing:boulder=no",
- "then": {
- "de": "Hier kann nicht gebouldert werden",
- "en": "Bouldering is not possible here",
- "nl": "Bolderen kan hier niet",
- "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"
+ "id": "Access description (without _embedding_feature:access:description)",
+ "render": "{access:description}",
+ "freeform": {
+ "key": "access:description"
}
},
- {
- "if": "climbing:boulder=limited",
- "then": {
- "de": "Bouldern ist hier nur an wenigen Routen möglich",
- "en": "Bouldering is possible, allthough there are only a few routes",
- "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"
- }
- },
- {
- "if": "climbing:boulder~*",
- "then": {
- "de": "Hier gibt es {climbing:boulder} Boulder-Routen",
- "en": "There are {climbing:boulder} boulder routes",
- "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"
- },
- "hideInAnswer": true
- }
- ],
- "condition": {
- "and": [
- {
- "or": [
- "climbing:sport=yes",
- "sport=climbing"
- ]
- },
- "office=",
- "club="
- ]
- }
- },
- {
- "id": "Toproping?",
- "question": {
- "de": "Ist Toprope-Klettern hier möglich?",
- "en": "Is toprope climbing possible here?",
- "nl": "Is het mogelijk om hier te toprope-klimmen?",
- "ja": "ここでtoprope登坂はできますか?",
- "fr": "Est-il possible d’escalader à la moulinette ?",
- "it": "È possibile arrampicarsi con la corda dall’alto qua?"
- },
- "mappings": [
- {
- "if": "climbing:toprope=yes",
- "then": {
- "de": "Toprope-Klettern ist hier möglich",
- "en": "Toprope climbing is possible here",
- "nl": "Toprope-klimmen kan hier",
- "ja": "ここでToprope登坂ができます",
- "fr": "L’escalade à la moulinette est possible",
- "it": "È possibile arrampicarsi con moulinette qua"
- }
- },
- {
- "if": "climbing:toprope=no",
- "then": {
- "de": "Toprope-Climbing ist hier nicht möglich",
- "en": "Toprope climbing is not possible here",
- "nl": "Toprope-klimmen kan hier niet",
- "ja": "ここではToprope登坂はできません",
- "fr": "L’escalade à la moulinette n’est pas possible",
- "it": "Non è possibile arrampicarsi con moulinette qua"
- }
- },
- {
- "if": "climbing:toprope~*",
- "then": {
- "de": "Hier gibt es {climbing:toprope} Toprope-Routen",
- "en": "There are {climbing:toprope} toprope routes",
- "nl": "Er zijn hier {climbing:toprope} toprope routes",
- "ja": "{climbing:toprope} 登坂ルートがある",
- "fr": "{climbing:toprope} voies sont équipées de moulinettes",
- "it": "Sono presenti {climbing:toprope} vie con moulinette"
- },
- "hideInAnswer": true
- }
- ],
- "condition": {
- "and": [
- {
- "or": [
- "climbing:sport=yes",
- "sport=climbing"
- ]
- },
- "office=",
- "club="
- ]
- }
- },
- {
- "id": "Sportclimbing?",
- "question": {
- "de": "Ist hier Sportklettern möglich (feste Ankerpunkte)?",
- "en": "Is sport climbing possible here on fixed anchors?",
- "nl": "Is het mogelijk om hier te sportklimmen/voorklimmen op reeds aangebrachte haken?",
- "ja": "ここでは固定アンカー式のスポーツクライミングはできますか?",
- "it": "È possibile arrampicarsi qua con ancoraggi fissi?"
- },
- "mappings": [
- {
- "if": "climbing:sport=yes",
- "then": {
- "de": "Sportklettern ist hier möglich",
- "en": "Sport climbing is possible here",
- "nl": "Sportklimmen/voorklimmen kan hier",
- "ru": "Здесь можно заняться спортивным скалолазанием",
- "ja": "ここでスポーツクライミングができます",
- "it": "L’arrampicata sportiva è possibile qua",
- "hu": "Itt lehetőség van sportmászásra",
- "fr": "De l’escalade est possible ici"
- }
- },
- {
- "if": "climbing:sport=no",
- "then": {
- "de": "Sportklettern ist hier nicht möglich",
- "en": "Sport climbing is not possible here",
- "nl": "Sportklimmen/voorklimmen kan hier niet",
- "ru": "Спортивное скалолазание здесь невозможно",
- "ja": "ここではスポーツクライミングはできません",
- "it": "L’arrampicata sportiva non è possibile qua",
- "hu": "Itt nincs lehetőség sportmászásra",
- "fr": "L’escalade est impossible ici"
- }
- },
- {
- "if": "climbing:sport~*",
- "then": {
- "de": "Hier gibt es {climbing:sport} Sportkletter-Routen",
- "en": "There are {climbing:sport} sport climbing routes",
- "nl": "Er zijn hier {climbing:sport} sportklimroutes/voorklimroutes",
- "ja": "スポーツクライミングの {climbing:sport} ルートがある",
- "it": "Sono presenti {climbing:sport} vie di arrampicata sportiva"
- },
- "hideInAnswer": true
- }
- ],
- "condition": {
- "and": [
- {
- "or": [
- "climbing:sport=yes",
- "sport=climbing"
- ]
- },
- "office=",
- "club="
- ]
- }
- },
- {
- "id": "Traditional climbing?",
- "question": {
- "de": "Ist hier traditionelles Klettern möglich (eigene Sicherung z.B. mit Klemmkleilen)?",
- "en": "Is traditional climbing possible here (using own gear e.g. chocks)?",
- "nl": "Is het mogelijk om hier traditioneel te klimmen? (Dit is klimmen met klemblokjes en friends) ",
- "ja": "伝統的な登山はここで可能ですか(例えば、チョックのような独自のギアを使用して)?",
- "it": "È possibile arrampicarsi in maniera tradizionale qua (usando attrezzi propri, ad es. dadi)?"
- },
- "mappings": [
- {
- "if": "climbing:traditional=yes",
- "then": {
- "de": "Traditionelles Klettern ist hier möglich",
- "en": "Traditional climbing is possible here",
- "nl": "Traditioneel klimmen kan hier",
- "ja": "ここでは伝統的な登山が可能です",
- "it": "L’arrampicata tradizionale è possibile qua"
- }
- },
- {
- "if": "climbing:traditional=no",
- "then": {
- "de": "Traditionelles Klettern ist hier nicht möglich",
- "en": "Traditional climbing is not possible here",
- "nl": "Traditioneel klimmen kan hier niet",
- "ja": "伝統的な登山はここではできない",
- "it": "L’arrampicata tradizionale non è possibile qua"
- }
- },
- {
- "if": "climbing:traditional~*",
- "then": {
- "de": "Hier gibt es {climbing:traditional} Routen für traditionelles Klettern",
- "en": "There are {climbing:traditional} traditional climbing routes",
- "nl": "Er zijn hier {climbing:traditional} traditionele klimroutes",
- "ja": "{climbing:traditional} の伝統的な登山ルートがある",
- "it": "Sono presenti {climbing:traditional} vie di arrampicata tradizionale"
- },
- "hideInAnswer": true
- }
- ],
- "condition": {
- "and": [
- {
- "or": [
- "climbing:sport=yes",
- "sport=climbing"
- ]
- },
- "office=",
- "club="
- ]
- }
- },
- {
- "id": "Speed climbing?",
- "question": {
- "de": "Gibt es hier eine Speedkletter-Wand?",
- "en": "Is there a speed climbing wall?",
- "nl": "Is er een snelklimmuur (speed climbing)?",
- "ja": "スピードクライミングウォールはありますか?",
- "it": "È presente una prete per l’arrampicata di velocità?"
- },
- "condition": {
- "and": [
- "leisure=sports_centre",
- {
- "or": [
- "climbing:sport=yes",
- "sport=climbing"
- ]
- },
- "office=",
- "club="
- ]
- },
- "mappings": [
- {
- "if": "climbing:speed=yes",
- "then": {
- "de": "Hier gibt es eine Speedkletter-Wand",
- "en": "There is a speed climbing wall",
- "nl": "Er is een snelklimmuur voor speed climbing",
- "ja": "スピードクライミングウォールがある",
- "it": "È presente una parete per l’arrampicata di velocità"
- }
- },
- {
- "if": "climbing:speed=no",
- "then": {
- "de": "Hier gibt es keine Speedkletter-Wand",
- "en": "There is no speed climbing wall",
- "nl": "Er is geen snelklimmuur voor speed climbing",
- "ja": "スピードクライミングウォールがない",
- "it": "Non è presente una parete per l’arrampicata di velocità"
- }
- },
- {
- "if": "climbing:speed~*",
- "then": {
- "de": "Hier gibt es {climbing:speed} Speedkletter-Routen",
- "en": "There are {climbing:speed} speed climbing walls",
- "nl": "Er zijn hier {climbing:speed} snelklimmuren",
- "ja": "{climbing:speed} のスピードクライミングウォールがある",
- "it": "Sono presenti {climbing:speed} pareti per l’arrampicata di velocità"
- },
- "hideInAnswer": true
- }
+ "questions",
+ "reviews"
]
- },
- "questions",
- "reviews"
- ]
- }
+ }
+ }
+ ]
}
\ No newline at end of file
diff --git a/assets/themes/climbing/css-test.html b/assets/themes/climbing/css-test.html
new file mode 100644
index 000000000..f96887813
--- /dev/null
+++ b/assets/themes/climbing/css-test.html
@@ -0,0 +1,23 @@
+
+
+
+
+ CSS-debugging for climbing theme
+
+
+
+
+
+ 2
+
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ X
+
+
+
\ No newline at end of file
diff --git a/assets/themes/grb_import/grb.json b/assets/themes/grb_import/grb.json
index 067a00ba2..b8abc0759 100644
--- a/assets/themes/grb_import/grb.json
+++ b/assets/themes/grb_import/grb.json
@@ -469,7 +469,7 @@
"_overlaps_with=feat.get('_overlaps_with_buildings').find(f => f.overlap > 1 /* square meter */ )",
"_osm_obj:source:ref=feat.get('_overlaps_with')?.feat?.properties['source:geometry:ref']",
"_osm_obj:id=feat.get('_overlaps_with')?.feat?.properties?.id",
- "_osm_obj:source:date=feat.get('_overlaps_with')?.feat?.properties['source:geometry:date'].replace(/\\//g, '-')",
+ "_osm_obj:source:date=(feat.get('_overlaps_with')?.feat?.properties ?? {})['source:geometry:date']?.replace(/\\//g, '-')",
"_osm_obj:building=feat.get('_overlaps_with')?.feat?.properties?.building",
"_osm_obj:addr:street=(feat.get('_overlaps_with')?.feat?.properties ?? {})['addr:street']",
"_osm_obj:addr:housenumber=(feat.get('_overlaps_with')?.feat?.properties ?? {})['addr:housenumber']",
diff --git a/assets/themes/natuurpunt/natuurpunt.css b/assets/themes/natuurpunt/natuurpunt.css
index 1839fd896..a8952f42e 100644
--- a/assets/themes/natuurpunt/natuurpunt.css
+++ b/assets/themes/natuurpunt/natuurpunt.css
@@ -20,12 +20,12 @@
@font-face{
font-family:"Open Sans Regular";
- src:url("./assets/themes/natuurpunt/fonts/OpenSans-Regular.ttf");
+ src:url("/assets/themes/natuurpunt/fonts/OpenSans-Regular.ttf");
}
@font-face{
font-family:"Amaranth";
- src:url("./assets/themes/natuurpunt/fonts/Amaranth-Regular.otf");
+ src:url("/assets/themes/natuurpunt/fonts/Amaranth-Regular.otf");
}
body {
@@ -109,4 +109,4 @@ h1, h2, h3, h4 {
.first-filter-panel {
/* Additional class on the first layer filter */
border-top: unset !important;
-}
\ No newline at end of file
+}
diff --git a/assets/themes/toerisme_vlaanderen/custom.css b/assets/themes/toerisme_vlaanderen/custom.css
index 2ea3bdf21..27a4894ba 100644
--- a/assets/themes/toerisme_vlaanderen/custom.css
+++ b/assets/themes/toerisme_vlaanderen/custom.css
@@ -4,12 +4,12 @@
@font-face{
font-family:"FlandersArt";
- src:url("./assets/themes/toerisme_vlaanderen/FlandersArtSans-Light.woff");
+ src:url("/assets/themes/toerisme_vlaanderen/FlandersArtSans-Light.woff");
}
@font-face{
font-family:"FlandersArtSerif";
- src:url("./assets/themes/toerisme_vlaanderen/FlandersArtSerif-Medium.woff");
+ src:url("/assets/themes/toerisme_vlaanderen/FlandersArtSerif-Medium.woff");
}
h1, h2, h3, h4 {
diff --git a/assets/themes/trees/trees.json b/assets/themes/trees/trees.json
index 910160fa5..393e0cadb 100644
--- a/assets/themes/trees/trees.json
+++ b/assets/themes/trees/trees.json
@@ -17,8 +17,8 @@
"es": "Árboles"
},
"shortDescription": {
- "nl": "Breng bomen in kaart",
"en": "Map all the trees",
+ "nl": "Breng bomen in kaart",
"fr": "Carte des arbres",
"it": "Mappa tutti gli alberi",
"ja": "すべての樹木をマッピングする",
diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css
index 6ae728e1b..da7f69aaa 100644
--- a/css/index-tailwind-output.css
+++ b/css/index-tailwind-output.css
@@ -1144,12 +1144,22 @@ video {
width: 2.75rem;
}
+.w-16 {
+ width: 4rem;
+}
+
.w-min {
width: -webkit-min-content;
width: -moz-min-content;
width: min-content;
}
+.w-16 {
+ width: 4rem;
+}
+
+.w-auto {
+ width: auto;
.w-1\/2 {
width: 50%;
}
@@ -1991,6 +2001,16 @@ a {
width: min-content;
}
+.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;
+}
+
.w-16-imp {
width: 4rem !important;
}
diff --git a/index.css b/index.css
index 77c7b0160..e70d93b25 100644
--- a/index.css
+++ b/index.css
@@ -39,6 +39,16 @@
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;
+ }
}
}
@@ -223,6 +233,16 @@ a {
width: min-content;
}
+.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;
+}
+
.w-16-imp {
width: 4rem !important;
}
diff --git a/langs/layers/ca.json b/langs/layers/ca.json
index 15daa3a58..7e1050a67 100644
--- a/langs/layers/ca.json
+++ b/langs/layers/ca.json
@@ -420,7 +420,7 @@
}
}
},
- "climbing": {
+ "climbing_area": {
"tagRenderings": {
"name": {
"render": "{name} "
@@ -441,13 +441,6 @@
}
}
},
- "climbing_gym": {
- "tagRenderings": {
- "name": {
- "render": "{name} "
- }
- }
- },
"climbing_opportunity": {
"tagRenderings": {
"climbing-opportunity-name": {
diff --git a/langs/layers/de.json b/langs/layers/de.json
index e51dc29c5..8198d6398 100644
--- a/langs/layers/de.json
+++ b/langs/layers/de.json
@@ -1631,7 +1631,84 @@
}
},
"climbing": {
- "description": "Eine Klettergelegenheit",
+ "tagRenderings": {
+ "average_length": {
+ "question": "Wie lang sind die Routen (durchschnittlich) in Metern?",
+ "render": "Die Routen sind durchschnittlich {canonical(climbing:length)} lang"
+ },
+ "bouldering": {
+ "mappings": {
+ "0": {
+ "then": "Hier kann gebouldert werden"
+ },
+ "1": {
+ "then": "Hier kann nicht gebouldert werden"
+ },
+ "2": {
+ "then": "Bouldern ist hier nur an wenigen Routen möglich"
+ },
+ "3": {
+ "then": "Hier gibt es {climbing:boulder} Boulder-Routen"
+ }
+ },
+ "question": "Kann hier gebouldert werden?"
+ },
+ "max_difficulty": {
+ "question": "Welche Schwierigkeit hat hier die schwerste Route (französisch/belgisches System)?",
+ "render": "Die schwierigste Route hat hier die Schwierigkeitsstufe {climbing:grade:french:max} (französisch/belgisches System)"
+ },
+ "min_difficulty": {
+ "question": "Welche Schwierigkeit hat hier die leichteste Route (französisch/belgisches System)?",
+ "render": "Die leichteste Route hat hier die Schwierigkeit {climbing:grade:french:min} (französisch/belgisches System)"
+ },
+ "sportclimbing": {
+ "mappings": {
+ "0": {
+ "then": "Sportklettern ist hier möglich"
+ },
+ "1": {
+ "then": "Sportklettern ist hier nicht möglich"
+ },
+ "2": {
+ "then": "Hier gibt es {climbing:sport} Sportkletter-Routen"
+ }
+ },
+ "question": "Ist hier Sportklettern möglich (feste Ankerpunkte)?"
+ },
+ "toprope": {
+ "mappings": {
+ "0": {
+ "then": "Toprope-Klettern ist hier möglich"
+ },
+ "1": {
+ "then": "Toprope-Climbing ist hier nicht möglich"
+ },
+ "2": {
+ "then": "Hier gibt es {climbing:toprope} Toprope-Routen"
+ }
+ },
+ "question": "Ist Toprope-Klettern hier möglich?"
+ },
+ "trad_climbing": {
+ "mappings": {
+ "0": {
+ "then": "Traditionelles Klettern ist hier möglich"
+ },
+ "1": {
+ "then": "Traditionelles Klettern ist hier nicht möglich"
+ },
+ "2": {
+ "then": "Hier gibt es {climbing:traditional} Routen für traditionelles Klettern"
+ }
+ },
+ "question": "Ist hier traditionelles Klettern möglich (eigene Sicherung z.B. mit Klemmkleilen)?"
+ },
+ "website": {
+ "question": "Gibt es eine (inoffizielle) Website mit mehr Informationen (z.B. Topos)?"
+ }
+ }
+ },
+ "climbing_area": {
"name": "Klettermöglichkeiten",
"presets": {
"0": {
@@ -1728,9 +1805,22 @@
"description": "Eine Kletterhalle",
"name": "Kletterhallen",
"tagRenderings": {
+ "Speed climbing?": {
+ "mappings": {
+ "0": {
+ "then": "Hier gibt es eine Speedkletter-Wand"
+ },
+ "1": {
+ "then": "Hier gibt es keine Speedkletter-Wand"
+ },
+ "2": {
+ "then": "Hier gibt es {climbing:speed} Speedkletter-Routen"
+ }
+ },
+ "question": "Gibt es hier eine Speedkletter-Wand?"
+ },
"name": {
- "question": "Wie heißt diese Kletterhalle?",
- "render": "{name} "
+ "question": "Wie heißt diese Kletterhalle?"
}
},
"title": {
@@ -1743,7 +1833,6 @@
}
},
"climbing_opportunity": {
- "description": "Eine Klettergelegenheit?",
"name": "Klettermöglichkeiten?",
"tagRenderings": {
"climbing-opportunity-name": {
@@ -1776,18 +1865,6 @@
}
},
"tagRenderings": {
- "Bolts": {
- "mappings": {
- "0": {
- "then": "Auf dieser Kletterroute sind keine Haken vorhanden"
- },
- "1": {
- "then": "Auf dieser Kletterroute sind keine Haken vorhanden"
- }
- },
- "question": "Wie viele Haken gibt es auf dieser Kletterroute bevor der Umlenker bzw. Standhaken erreicht ist?",
- "render": "Diese Kletterroute hat {climbing:bolts} Haken"
- },
"Difficulty": {
"question": "Wie hoch ist der Schwierigkeitsgrad dieser Kletterroute nach dem französisch/belgischen System?",
"render": "Die Schwierigkeit ist {climbing:grade:french} entsprechend des französisch/belgischen Systems"
@@ -1805,8 +1882,17 @@
"question": "Wie heißt diese Kletterroute?",
"render": "{name} "
},
- "Rock type": {
+ "Rock type via embedded feature": {
"render": "Der Gesteinstyp ist {_embedding_features_with_rock:rock}, wie auf dem umgebenden Felsen angegeben "
+ },
+ "bolts": {
+ "mappings": {
+ "0": {
+ "then": "Auf dieser Kletterroute sind keine Haken vorhanden"
+ }
+ },
+ "question": "Wie viele Haken gibt es auf dieser Kletterroute bevor der Umlenker bzw. Standhaken erreicht ist?",
+ "render": "Diese Kletterroute hat {climbing:bolts} Haken"
}
},
"title": {
diff --git a/langs/layers/en.json b/langs/layers/en.json
index 0a01ff33b..5b493f322 100644
--- a/langs/layers/en.json
+++ b/langs/layers/en.json
@@ -2303,7 +2303,102 @@
}
},
"climbing": {
- "description": "A climbing opportunity",
+ "description": "A dummy layer which contains tagrenderings, shared among the climbing layers",
+ "tagRenderings": {
+ "average_length": {
+ "question": "What is the (average) length of the routes in meters?",
+ "render": "The routes are {canonical(climbing:length)} long on average"
+ },
+ "bouldering": {
+ "mappings": {
+ "0": {
+ "then": "Bouldering is possible here"
+ },
+ "1": {
+ "then": "Bouldering is not possible here"
+ },
+ "2": {
+ "then": "Bouldering is possible, allthough there are only a few routes"
+ },
+ "3": {
+ "then": "There are {climbing:boulder} boulder routes"
+ }
+ },
+ "question": "Is bouldering possible here?"
+ },
+ "fee": {
+ "mappings": {
+ "0": {
+ "then": "Climbing here is free of charge"
+ },
+ "1": {
+ "then": "Paying a fee is required to climb here"
+ }
+ },
+ "question": "Is a fee required to climb here?",
+ "render": "A fee of {charge} should be paid for climbing here"
+ },
+ "max_bolts": {
+ "question": "How many bolts do routes in {title()} have at most?",
+ "render": "The sport climbing routes here have at most {climbing:bolts:max} bolts.This is without relays and indicates how much quickdraws a climber needs
"
+ },
+ "max_difficulty": {
+ "question": "What is the highest grade route here, according to the french classification system?",
+ "render": "The highest grade is {climbing:grade:french:max} according to the french/belgian system"
+ },
+ "min_difficulty": {
+ "question": "What is the grade of the easiest route here, according to the french classification system?",
+ "render": "The lowest grade is {climbing:grade:french:min} according to the french/belgian system"
+ },
+ "sportclimbing": {
+ "mappings": {
+ "0": {
+ "then": "Sport climbing is possible here"
+ },
+ "1": {
+ "then": "Sport climbing is not possible here"
+ },
+ "2": {
+ "then": "There are {climbing:sport} sport climbing routes"
+ }
+ },
+ "question": "Is sport climbing possible here on fixed anchors?"
+ },
+ "toprope": {
+ "mappings": {
+ "0": {
+ "then": "Toprope climbing is possible here"
+ },
+ "1": {
+ "then": "Toprope climbing is not possible here"
+ },
+ "2": {
+ "then": "There are {climbing:toprope} toprope routes"
+ }
+ },
+ "question": "Is toprope climbing possible here?"
+ },
+ "trad_climbing": {
+ "mappings": {
+ "0": {
+ "then": "Traditional climbing is possible here"
+ },
+ "1": {
+ "then": "Traditional climbing is not possible here"
+ },
+ "2": {
+ "then": "There are {climbing:traditional} traditional climbing routes"
+ }
+ },
+ "question": "Is traditional climbing possible here (using own gear e.g. chocks)?"
+ },
+ "website": {
+ "question": "Is there a (unofficial) website with more informations (e.g. topos)?"
+ }
+ }
+ },
+ "climbing_area": {
+ "description": "An area where climbing is possible, e.g. a crag, site, boulder, ... Contains aggregation of routes",
"name": "Climbing opportunities",
"presets": {
"0": {
@@ -2400,9 +2495,22 @@
"description": "A climbing gym",
"name": "Climbing gyms",
"tagRenderings": {
+ "Speed climbing?": {
+ "mappings": {
+ "0": {
+ "then": "There is a speed climbing wall"
+ },
+ "1": {
+ "then": "There is no speed climbing wall"
+ },
+ "2": {
+ "then": "There are {climbing:speed} speed climbing walls"
+ }
+ },
+ "question": "Is there a speed climbing wall?"
+ },
"name": {
- "question": "What is the name of this climbing gym?",
- "render": "{name} "
+ "question": "What is the name of this climbing gym?"
}
},
"title": {
@@ -2415,7 +2523,7 @@
}
},
"climbing_opportunity": {
- "description": "A climbing opportunity?",
+ "description": "Fallback layer with items on which climbing _might_ be possible. It is loaded when zoomed in a lot, to prevent duplicate items to be added",
"name": "Climbing opportunities?",
"tagRenderings": {
"climbing-opportunity-name": {
@@ -2441,6 +2549,7 @@
}
},
"climbing_route": {
+ "description": "A single climbing route and its properties. Some properties are derived from the containing features",
"name": "Climbing routes",
"presets": {
"0": {
@@ -2448,18 +2557,6 @@
}
},
"tagRenderings": {
- "Bolts": {
- "mappings": {
- "0": {
- "then": "This route is not bolted"
- },
- "1": {
- "then": "This route is not bolted"
- }
- },
- "question": "How many bolts does this route have before reaching the anchor?",
- "render": "This route has {climbing:bolts} bolts"
- },
"Difficulty": {
"question": "What is the grade of this climbing route according to the french/belgian system?",
"render": "The grade is {climbing:grade:french} according to the french/belgian system"
@@ -2477,8 +2574,17 @@
"question": "What is the name of this climbing route?",
"render": "{name} "
},
- "Rock type": {
+ "Rock type via embedded feature": {
"render": "The rock type is {_embedding_features_with_rock:rock} as stated on the surrounding crag "
+ },
+ "bolts": {
+ "mappings": {
+ "0": {
+ "then": "This route is not bolted"
+ }
+ },
+ "question": "How many bolts does this route have before reaching the anchor?",
+ "render": "This route has {climbing:bolts} bolts This is without relays and indicates how much quickdraws a climber needs
"
}
},
"title": {
@@ -5482,6 +5588,9 @@
},
"question": "Is this a broadleaved or needleleaved tree?"
},
+ "tree-species-wikidata": {
+ "question": "What species is this tree?"
+ },
"tree_node-name": {
"mappings": {
"0": {
diff --git a/langs/layers/fr.json b/langs/layers/fr.json
index 702021fff..90d813408 100644
--- a/langs/layers/fr.json
+++ b/langs/layers/fr.json
@@ -1036,7 +1036,66 @@
}
},
"climbing": {
- "description": "Opportunité d’escalade",
+ "tagRenderings": {
+ "average_length": {
+ "question": "Quelle est la longueur moyenne des voies en mètres ?",
+ "render": "Les voies font {canonical(climbing:length)} de long en moyenne"
+ },
+ "bouldering": {
+ "mappings": {
+ "0": {
+ "then": "L’escalade de bloc est possible"
+ },
+ "1": {
+ "then": "L’escalade de bloc n’est pas possible"
+ },
+ "2": {
+ "then": "L’escalade de bloc est possible sur des voies précises"
+ },
+ "3": {
+ "then": "Il y a {climbing:boulder} voies d’escalade de bloc"
+ }
+ },
+ "question": "L’escalade de bloc est-elle possible ici ?"
+ },
+ "max_difficulty": {
+ "question": "Quel est le niveau de la voie la plus difficile selon la classification franco-belge ?",
+ "render": "La difficulté maximale est {climbing:grade:french:max} selon la classification franco-belge"
+ },
+ "min_difficulty": {
+ "question": "Quel est le niveau de la voie la plus simple selon la classification franco-belge ?",
+ "render": "La difficulté minimale est {climbing:grade:french:min} selon la classification franco-belge"
+ },
+ "sportclimbing": {
+ "mappings": {
+ "0": {
+ "then": "De l’escalade est possible ici"
+ },
+ "1": {
+ "then": "L’escalade est impossible ici"
+ }
+ }
+ },
+ "toprope": {
+ "mappings": {
+ "0": {
+ "then": "L’escalade à la moulinette est possible"
+ },
+ "1": {
+ "then": "L’escalade à la moulinette n’est pas possible"
+ },
+ "2": {
+ "then": "{climbing:toprope} voies sont équipées de moulinettes"
+ }
+ },
+ "question": "Est-il possible d’escalader à la moulinette ?"
+ },
+ "website": {
+ "question": "Existe-t’il un site avec plus d’informations (ex : topographie) ?"
+ }
+ }
+ },
+ "climbing_area": {
"name": "Opportunité d’escalade",
"presets": {
"0": {
@@ -1134,8 +1193,7 @@
"name": "Salle d’escalade",
"tagRenderings": {
"name": {
- "question": "Quel est le nom de la salle d’escalade ?",
- "render": "{name} "
+ "question": "Quel est le nom de la salle d’escalade ?"
}
},
"title": {
@@ -1148,7 +1206,6 @@
}
},
"climbing_opportunity": {
- "description": "Opportunité d’escalade ?",
"name": "Opportunités d’escalade ?",
"tagRenderings": {
"climbing-opportunity-name": {
@@ -1181,18 +1238,6 @@
}
},
"tagRenderings": {
- "Bolts": {
- "mappings": {
- "0": {
- "then": "Cette voie n’a pas de prises"
- },
- "1": {
- "then": "Cette voie n’a pas de prises"
- }
- },
- "question": "Combien de prises cette voie possède avant d’atteindre la moulinette ?",
- "render": "Cette voie a {climbing:bolts} prises"
- },
"Difficulty": {
"question": "Quelle est la difficulté de cette voie selon le système franco-belge ?",
"render": "Selon le système franco-belge, la difficulté de cette voie est de {climbing:grade:french}"
@@ -1210,8 +1255,17 @@
"question": "Quel est le nom de cette voie d’escalade ?",
"render": "{name} "
},
- "Rock type": {
+ "Rock type via embedded feature": {
"render": "Le type de roche est {_embedding_features_with_rock:rock} selon le mur "
+ },
+ "bolts": {
+ "mappings": {
+ "0": {
+ "then": "Cette voie n’a pas de prises"
+ }
+ },
+ "question": "Combien de prises cette voie possède avant d’atteindre la moulinette ?",
+ "render": "Cette voie a {climbing:bolts} prises"
}
},
"title": {
diff --git a/langs/layers/hu.json b/langs/layers/hu.json
index 5bed8bef6..e85e53de4 100644
--- a/langs/layers/hu.json
+++ b/langs/layers/hu.json
@@ -474,6 +474,20 @@
}
}
},
+ "climbing": {
+ "tagRenderings": {
+ "sportclimbing": {
+ "mappings": {
+ "0": {
+ "then": "Itt lehetőség van sportmászásra"
+ },
+ "1": {
+ "then": "Itt nincs lehetőség sportmászásra"
+ }
+ }
+ }
+ }
+ },
"climbing_club": {
"description": "Mászóegyesület vagy -szervezet",
"name": "Mászóegyesület",
diff --git a/langs/layers/id.json b/langs/layers/id.json
index 12c017556..dabc2bbed 100644
--- a/langs/layers/id.json
+++ b/langs/layers/id.json
@@ -151,7 +151,7 @@
}
}
},
- "climbing": {
+ "climbing_area": {
"tagRenderings": {
"name": {
"render": "{name} "
@@ -165,13 +165,6 @@
}
}
},
- "climbing_gym": {
- "tagRenderings": {
- "name": {
- "render": "{name} "
- }
- }
- },
"climbing_opportunity": {
"tagRenderings": {
"climbing-opportunity-name": {
diff --git a/langs/layers/it.json b/langs/layers/it.json
index 859806f3a..09f17d593 100644
--- a/langs/layers/it.json
+++ b/langs/layers/it.json
@@ -792,7 +792,84 @@
}
},
"climbing": {
- "description": "Un’opportunità di arrampicata",
+ "tagRenderings": {
+ "average_length": {
+ "question": "Quale è la lunghezza (media) delle vie in metri?",
+ "render": "Le vie sono lunghe mediamente {canonical(climbing:length)} "
+ },
+ "bouldering": {
+ "mappings": {
+ "0": {
+ "then": "L’arrampicata su massi è possibile qua"
+ },
+ "1": {
+ "then": "L’arrampicata su massi non è possibile qua"
+ },
+ "2": {
+ "then": "L’arrampicata su massi è possibile anche se su poche vie"
+ },
+ "3": {
+ "then": "Sono presenti {climbing:boulder} vie di arrampicata su massi"
+ }
+ },
+ "question": "È possibile praticare ‘bouldering’ qua?"
+ },
+ "max_difficulty": {
+ "question": "Qual è il livello della via più difficile qua, secondo il sistema di classificazione francese?",
+ "render": "Il massimo livello di difficoltà è {climbing:grade:french:max} secondo il sistema francese/belga"
+ },
+ "min_difficulty": {
+ "question": "Qual è il livello della via più facile qua, secondo il sistema di classificazione francese?",
+ "render": "Il minimo livello di difficoltà è {climbing:grade:french:min} secondo il sistema francese/belga"
+ },
+ "sportclimbing": {
+ "mappings": {
+ "0": {
+ "then": "L’arrampicata sportiva è possibile qua"
+ },
+ "1": {
+ "then": "L’arrampicata sportiva non è possibile qua"
+ },
+ "2": {
+ "then": "Sono presenti {climbing:sport} vie di arrampicata sportiva"
+ }
+ },
+ "question": "È possibile arrampicarsi qua con ancoraggi fissi?"
+ },
+ "toprope": {
+ "mappings": {
+ "0": {
+ "then": "È possibile arrampicarsi con moulinette qua"
+ },
+ "1": {
+ "then": "Non è possibile arrampicarsi con moulinette qua"
+ },
+ "2": {
+ "then": "Sono presenti {climbing:toprope} vie con moulinette"
+ }
+ },
+ "question": "È possibile arrampicarsi con la corda dall’alto qua?"
+ },
+ "trad_climbing": {
+ "mappings": {
+ "0": {
+ "then": "L’arrampicata tradizionale è possibile qua"
+ },
+ "1": {
+ "then": "L’arrampicata tradizionale non è possibile qua"
+ },
+ "2": {
+ "then": "Sono presenti {climbing:traditional} vie di arrampicata tradizionale"
+ }
+ },
+ "question": "È possibile arrampicarsi in maniera tradizionale qua (usando attrezzi propri, ad es. dadi)?"
+ },
+ "website": {
+ "question": "C’è un sito web (anche non ufficiale) con qualche informazione in più (ad es. topografie)?"
+ }
+ }
+ },
+ "climbing_area": {
"name": "Opportunità di arrampicata",
"presets": {
"0": {
@@ -889,9 +966,22 @@
"description": "Una palestra di arrampicata",
"name": "Palestre di arrampicata",
"tagRenderings": {
+ "Speed climbing?": {
+ "mappings": {
+ "0": {
+ "then": "È presente una parete per l’arrampicata di velocità"
+ },
+ "1": {
+ "then": "Non è presente una parete per l’arrampicata di velocità"
+ },
+ "2": {
+ "then": "Sono presenti {climbing:speed} pareti per l’arrampicata di velocità"
+ }
+ },
+ "question": "È presente una prete per l’arrampicata di velocità?"
+ },
"name": {
- "question": "Qual è il nome di questa palestra di arrampicata?",
- "render": "{name} "
+ "question": "Qual è il nome di questa palestra di arrampicata?"
}
},
"title": {
@@ -904,7 +994,6 @@
}
},
"climbing_opportunity": {
- "description": "Un’opportunità di arrampicata?",
"name": "Opportunità di arrampicata?",
"tagRenderings": {
"climbing-opportunity-name": {
@@ -937,18 +1026,6 @@
}
},
"tagRenderings": {
- "Bolts": {
- "mappings": {
- "0": {
- "then": "In questo percorso non sono presenti bulloni"
- },
- "1": {
- "then": "In questo percorso non sono presenti bulloni"
- }
- },
- "question": "Quanti bulloni sono presenti in questo percorso prima di arrivare alla moulinette?",
- "render": "Questo percorso ha {climbing:bolts} bulloni"
- },
"Difficulty": {
"question": "Qual è la difficoltà di questa via di arrampicata nel sistema francese/belga?",
"render": "Il grado di difficoltà è {climbing:grade:french} nel sistema francese/belga"
@@ -966,8 +1043,17 @@
"question": "Come si chiama questa via di arrampicata?",
"render": "{name} "
},
- "Rock type": {
+ "Rock type via embedded feature": {
"render": "Il tipo di roccia è {_embedding_features_with_rock:rock} come dichiarato sul muro circostante "
+ },
+ "bolts": {
+ "mappings": {
+ "0": {
+ "then": "In questo percorso non sono presenti bulloni"
+ }
+ },
+ "question": "Quanti bulloni sono presenti in questo percorso prima di arrivare alla moulinette?",
+ "render": "Questo percorso ha {climbing:bolts} bulloni"
}
},
"title": {
diff --git a/langs/layers/ja.json b/langs/layers/ja.json
index b8075b171..a7f4631cd 100644
--- a/langs/layers/ja.json
+++ b/langs/layers/ja.json
@@ -122,7 +122,84 @@
}
},
"climbing": {
- "description": "登坂教室",
+ "tagRenderings": {
+ "average_length": {
+ "question": "ルートの(平均)長さはメートル単位でいくつですか?",
+ "render": "ルートの長さは平均で{canonical(climbing:length)} です"
+ },
+ "bouldering": {
+ "mappings": {
+ "0": {
+ "then": "ボルダリングはここで可能です"
+ },
+ "1": {
+ "then": "ここではボルダリングはできません"
+ },
+ "2": {
+ "then": "ボルダリングは可能ですが、少しのルートしかありません"
+ },
+ "3": {
+ "then": "{climbing:boulder} ボルダールートがある"
+ }
+ },
+ "question": "ここでボルダリングはできますか?"
+ },
+ "max_difficulty": {
+ "question": "フランスのランク評価によると、ここで一番難しいルートのレベルはどれくらいですか?",
+ "render": "フランス/ベルギーのランク評価システムでは、最大の難易度は{climbing:grade:french:max}です"
+ },
+ "min_difficulty": {
+ "question": "ここで一番簡単なルートのレベルは、フランスのランク評価システムで何ですか?",
+ "render": "フランス/ベルギーのランク評価システムでは、最小の難易度は{climbing:grade:french:min}です"
+ },
+ "sportclimbing": {
+ "mappings": {
+ "0": {
+ "then": "ここでスポーツクライミングができます"
+ },
+ "1": {
+ "then": "ここではスポーツクライミングはできません"
+ },
+ "2": {
+ "then": "スポーツクライミングの {climbing:sport} ルートがある"
+ }
+ },
+ "question": "ここでは固定アンカー式のスポーツクライミングはできますか?"
+ },
+ "toprope": {
+ "mappings": {
+ "0": {
+ "then": "ここでToprope登坂ができます"
+ },
+ "1": {
+ "then": "ここではToprope登坂はできません"
+ },
+ "2": {
+ "then": "{climbing:toprope} 登坂ルートがある"
+ }
+ },
+ "question": "ここでtoprope登坂はできますか?"
+ },
+ "trad_climbing": {
+ "mappings": {
+ "0": {
+ "then": "ここでは伝統的な登山が可能です"
+ },
+ "1": {
+ "then": "伝統的な登山はここではできない"
+ },
+ "2": {
+ "then": "{climbing:traditional} の伝統的な登山ルートがある"
+ }
+ },
+ "question": "伝統的な登山はここで可能ですか(例えば、チョックのような独自のギアを使用して)?"
+ },
+ "website": {
+ "question": "もっと情報のある(非公式の)ウェブサイトはありますか(例えば、topos)?"
+ }
+ }
+ },
+ "climbing_area": {
"name": "登坂教室",
"presets": {
"0": {
@@ -177,9 +254,22 @@
"description": "クライミングジム",
"name": "クライミングジム",
"tagRenderings": {
+ "Speed climbing?": {
+ "mappings": {
+ "0": {
+ "then": "スピードクライミングウォールがある"
+ },
+ "1": {
+ "then": "スピードクライミングウォールがない"
+ },
+ "2": {
+ "then": "{climbing:speed} のスピードクライミングウォールがある"
+ }
+ },
+ "question": "スピードクライミングウォールはありますか?"
+ },
"name": {
- "question": "このクライミングジムは何という名前ですか?",
- "render": "{name} "
+ "question": "このクライミングジムは何という名前ですか?"
}
},
"title": {
@@ -192,7 +282,6 @@
}
},
"climbing_opportunity": {
- "description": "登坂教室?",
"name": "登坂教室?",
"tagRenderings": {
"climbing-opportunity-name": {
diff --git a/langs/layers/nb_NO.json b/langs/layers/nb_NO.json
index 487780cc5..d6d054e45 100644
--- a/langs/layers/nb_NO.json
+++ b/langs/layers/nb_NO.json
@@ -196,7 +196,21 @@
}
},
"climbing": {
- "description": "En klatremulighet",
+ "tagRenderings": {
+ "bouldering": {
+ "mappings": {
+ "0": {
+ "then": "Buldring er mulig her"
+ },
+ "1": {
+ "then": "Buldring er ikke mulig her"
+ }
+ },
+ "question": "Er buldring mulig her?"
+ }
+ }
+ },
+ "climbing_area": {
"presets": {
"0": {
"description": "En klatremulighet",
@@ -221,7 +235,6 @@
}
},
"climbing_opportunity": {
- "description": "En klatremulighet?",
"name": "Klatremuligheter?",
"tagRenderings": {
"climbing-possible": {
diff --git a/langs/layers/nl.json b/langs/layers/nl.json
index fd581e459..4cfc56cb7 100644
--- a/langs/layers/nl.json
+++ b/langs/layers/nl.json
@@ -2312,7 +2312,84 @@
}
},
"climbing": {
- "description": "Een klimgelegenheid",
+ "tagRenderings": {
+ "average_length": {
+ "question": "Wat is de (gemiddelde) lengte van de klimroutes, in meter?",
+ "render": "De klimroutes zijn gemiddeld {canonical(climbing:length)} lang"
+ },
+ "bouldering": {
+ "mappings": {
+ "0": {
+ "then": "Bolderen kan hier"
+ },
+ "1": {
+ "then": "Bolderen kan hier niet"
+ },
+ "2": {
+ "then": "Bolderen kan hier, maar er zijn niet zoveel routes"
+ },
+ "3": {
+ "then": "Er zijn hier {climbing:boulder} bolderroutes"
+ }
+ },
+ "question": "Is het mogelijk om hier te bolderen?"
+ },
+ "max_difficulty": {
+ "question": "Wat is het niveau van de moeilijkste route, volgens het Franse classificatiesysteem?",
+ "render": "De maximale klimmoeilijkheid is {climbing:grade:french:max} volgens het Franse/Belgische systeem"
+ },
+ "min_difficulty": {
+ "question": "Wat is het niveau van de makkelijkste route, volgens het Franse classificatiesysteem?",
+ "render": "De minimale klimmoeilijkheid is {climbing:grade:french:min} volgens het Franse/Belgische systeem"
+ },
+ "sportclimbing": {
+ "mappings": {
+ "0": {
+ "then": "Sportklimmen/voorklimmen kan hier"
+ },
+ "1": {
+ "then": "Sportklimmen/voorklimmen kan hier niet"
+ },
+ "2": {
+ "then": "Er zijn hier {climbing:sport} sportklimroutes/voorklimroutes"
+ }
+ },
+ "question": "Is het mogelijk om hier te sportklimmen/voorklimmen op reeds aangebrachte haken?"
+ },
+ "toprope": {
+ "mappings": {
+ "0": {
+ "then": "Toprope-klimmen kan hier"
+ },
+ "1": {
+ "then": "Toprope-klimmen kan hier niet"
+ },
+ "2": {
+ "then": "Er zijn hier {climbing:toprope} toprope routes"
+ }
+ },
+ "question": "Is het mogelijk om hier te toprope-klimmen?"
+ },
+ "trad_climbing": {
+ "mappings": {
+ "0": {
+ "then": "Traditioneel klimmen kan hier"
+ },
+ "1": {
+ "then": "Traditioneel klimmen kan hier niet"
+ },
+ "2": {
+ "then": "Er zijn hier {climbing:traditional} traditionele klimroutes"
+ }
+ },
+ "question": "Is het mogelijk om hier traditioneel te klimmen? (Dit is klimmen met klemblokjes en friends) "
+ },
+ "website": {
+ "question": "Is er een (onofficiële) website met meer informatie (b.v. met topos)?"
+ }
+ }
+ },
+ "climbing_area": {
"name": "Klimgelegenheden",
"presets": {
"0": {
@@ -2385,9 +2462,22 @@
"description": "Een klimzaal",
"name": "Klimzalen",
"tagRenderings": {
+ "Speed climbing?": {
+ "mappings": {
+ "0": {
+ "then": "Er is een snelklimmuur voor speed climbing"
+ },
+ "1": {
+ "then": "Er is geen snelklimmuur voor speed climbing"
+ },
+ "2": {
+ "then": "Er zijn hier {climbing:speed} snelklimmuren"
+ }
+ },
+ "question": "Is er een snelklimmuur (speed climbing)?"
+ },
"name": {
- "question": "Wat is de naam van dit Klimzaal?",
- "render": "{name} "
+ "question": "Wat is de naam van dit Klimzaal?"
}
},
"title": {
@@ -2400,7 +2490,6 @@
}
},
"climbing_opportunity": {
- "description": "Een klimgelegenheid?",
"name": "Klimgelegenheiden?",
"tagRenderings": {
"climbing-opportunity-name": {
diff --git a/langs/layers/ru.json b/langs/layers/ru.json
index fca36af7e..9d4b2bc2c 100644
--- a/langs/layers/ru.json
+++ b/langs/layers/ru.json
@@ -764,6 +764,23 @@
}
},
"climbing": {
+ "tagRenderings": {
+ "sportclimbing": {
+ "mappings": {
+ "0": {
+ "then": "Здесь можно заняться спортивным скалолазанием"
+ },
+ "1": {
+ "then": "Спортивное скалолазание здесь невозможно"
+ }
+ }
+ },
+ "website": {
+ "question": "Есть ли (неофициальный) веб-сайт с более подробной информацией (напр., topos)?"
+ }
+ }
+ },
+ "climbing_area": {
"tagRenderings": {
"name": {
"render": "{name} "
@@ -790,11 +807,6 @@
"climbing_gym": {
"description": "Комплекс скалолазания",
"name": "Комплексы скалолазания",
- "tagRenderings": {
- "name": {
- "render": "{name} "
- }
- },
"title": {
"render": "Комплекс скалолазания"
}
diff --git a/langs/themes/ca.json b/langs/themes/ca.json
index 4fe033415..85a3e2436 100644
--- a/langs/themes/ca.json
+++ b/langs/themes/ca.json
@@ -49,27 +49,31 @@
"title": "Estacions de càrrega"
},
"climbing": {
- "overrideAll": {
- "tagRenderings+": {
- "2": {
- "mappings": {
- "2": {
- "then": "Només clients"
- },
- "3": {
- "then": "Només membres del club"
- }
- }
- }
- },
- "units+": {
- "0": {
- "applicableUnits": {
- "0": {
- "human": " metre"
- },
+ "layers": {
+ "0": {
+ "override": {
+ "tagRenderings+": {
"1": {
- "human": " peus"
+ "mappings": {
+ "2": {
+ "then": "Només clients"
+ },
+ "3": {
+ "then": "Només membres del club"
+ }
+ }
+ }
+ },
+ "units+": {
+ "0": {
+ "applicableUnits": {
+ "0": {
+ "human": " metre"
+ },
+ "1": {
+ "human": " peus"
+ }
+ }
}
}
}
diff --git a/langs/themes/de.json b/langs/themes/de.json
index d33c83651..6b96fea87 100644
--- a/langs/themes/de.json
+++ b/langs/themes/de.json
@@ -269,138 +269,54 @@
"climbing": {
"description": "Eine Karte mit Klettermöglichkeiten wie Kletterhallen, Kletterparks oder Felsen.",
"descriptionTail": "kletterspots.de wird betrieben von Christian Neumann . Bitte melden Sie sich , wenn Sie Feedback oder Fragen haben.
Das Projekt nutzt OpenStreetMap Daten und basiert auf der freien Software MapComplete .
",
- "overrideAll": {
- "tagRenderings+": {
- "0": {
- "question": "Gibt es eine (inoffizielle) Website mit mehr Informationen (z.B. Topos)?"
- },
- "1": {
- "mappings": {
+ "layers": {
+ "0": {
+ "override": {
+ "tagRenderings+": {
"0": {
- "then": "Das enthaltende Objekt gibt an, dass es öffentlich zugänglich ist {_embedding_feature:access:description}"
+ "mappings": {
+ "0": {
+ "then": "Das enthaltende Objekt gibt an, dass es öffentlich zugänglich ist {_embedding_feature:access:description}"
+ },
+ "1": {
+ "then": "Das enthaltende Objekt besagt, dass eine Genehmigung erforderlich ist für den Zugang zu {_embedding_feature:access:description}"
+ },
+ "2": {
+ "then": "Das enthaltende Objekt besagt, dass es nur für Kunden zugänglich ist {_embedding_feature:access:description}"
+ },
+ "3": {
+ "then": "Das enthaltende Objekt besagt, dass es nur für Mitglieder zugänglich ist {_embedding_feature:access:description}"
+ }
+ }
},
"1": {
- "then": "Das enthaltende Objekt besagt, dass eine Genehmigung erforderlich ist für den Zugang zu {_embedding_feature:access:description}"
- },
- "2": {
- "then": "Das enthaltende Objekt besagt, dass es nur für Kunden zugänglich ist {_embedding_feature:access:description}"
- },
- "3": {
- "then": "Das enthaltende Objekt besagt, dass es nur für Mitglieder zugänglich ist {_embedding_feature:access:description}"
- }
- }
- },
- "2": {
- "mappings": {
- "0": {
- "then": "Öffentlich zugänglich für jedermann"
- },
- "1": {
- "then": "Zugang nur mit Genehmigung"
- },
- "2": {
- "then": "Nur für Kunden"
- },
- "3": {
- "then": "Nur für Vereinsmitglieder"
+ "mappings": {
+ "0": {
+ "then": "Öffentlich zugänglich für jedermann"
+ },
+ "1": {
+ "then": "Zugang nur mit Genehmigung"
+ },
+ "2": {
+ "then": "Nur für Kunden"
+ },
+ "3": {
+ "then": "Nur für Vereinsmitglieder"
+ }
+ },
+ "question": "Wer hat hier Zugang?"
}
},
- "question": "Wer hat hier Zugang?"
- },
- "4": {
- "question": "Wie lang sind die Routen (durchschnittlich) in Metern?",
- "render": "Die Routen sind durchschnittlich {canonical(climbing:length)} lang"
- },
- "5": {
- "question": "Welche Schwierigkeit hat hier die leichteste Route (französisch/belgisches System)?",
- "render": "Die leichteste Route hat hier die Schwierigkeit {climbing:grade:french:min} (französisch/belgisches System)"
- },
- "6": {
- "question": "Welche Schwierigkeit hat hier die schwerste Route (französisch/belgisches System)?",
- "render": "Die schwierigste Route hat hier die Schwierigkeitsstufe {climbing:grade:french:max} (französisch/belgisches System)"
- },
- "7": {
- "mappings": {
+ "units+": {
"0": {
- "then": "Hier kann gebouldert werden"
- },
- "1": {
- "then": "Hier kann nicht gebouldert werden"
- },
- "2": {
- "then": "Bouldern ist hier nur an wenigen Routen möglich"
- },
- "3": {
- "then": "Hier gibt es {climbing:boulder} Boulder-Routen"
- }
- },
- "question": "Kann hier gebouldert werden?"
- },
- "8": {
- "mappings": {
- "0": {
- "then": "Toprope-Klettern ist hier möglich"
- },
- "1": {
- "then": "Toprope-Climbing ist hier nicht möglich"
- },
- "2": {
- "then": "Hier gibt es {climbing:toprope} Toprope-Routen"
- }
- },
- "question": "Ist Toprope-Klettern hier möglich?"
- },
- "9": {
- "mappings": {
- "0": {
- "then": "Sportklettern ist hier möglich"
- },
- "1": {
- "then": "Sportklettern ist hier nicht möglich"
- },
- "2": {
- "then": "Hier gibt es {climbing:sport} Sportkletter-Routen"
- }
- },
- "question": "Ist hier Sportklettern möglich (feste Ankerpunkte)?"
- },
- "10": {
- "mappings": {
- "0": {
- "then": "Traditionelles Klettern ist hier möglich"
- },
- "1": {
- "then": "Traditionelles Klettern ist hier nicht möglich"
- },
- "2": {
- "then": "Hier gibt es {climbing:traditional} Routen für traditionelles Klettern"
- }
- },
- "question": "Ist hier traditionelles Klettern möglich (eigene Sicherung z.B. mit Klemmkleilen)?"
- },
- "11": {
- "mappings": {
- "0": {
- "then": "Hier gibt es eine Speedkletter-Wand"
- },
- "1": {
- "then": "Hier gibt es keine Speedkletter-Wand"
- },
- "2": {
- "then": "Hier gibt es {climbing:speed} Speedkletter-Routen"
- }
- },
- "question": "Gibt es hier eine Speedkletter-Wand?"
- }
- },
- "units+": {
- "0": {
- "applicableUnits": {
- "0": {
- "human": " Meter"
- },
- "1": {
- "human": " Fuß"
+ "applicableUnits": {
+ "0": {
+ "human": " Meter"
+ },
+ "1": {
+ "human": " Fuß"
+ }
+ }
}
}
}
@@ -724,68 +640,6 @@
"shortDescription": "Hydranten, Feuerlöscher, Feuerwachen und Rettungswachen.",
"title": "Hydranten, Feuerlöscher, Feuerwachen und Rettungswachen"
},
- "mapcomplete-changes": {
- "description": "Diese Karte zeigt alle Änderungen die mit MapComplete gemacht wurden",
- "layers": {
- "0": {
- "description": "Zeigt alle MapComplete Änderungen",
- "filter": {
- "0": {
- "options": {
- "0": {
- "question": "Themenname enthält {search}"
- }
- }
- },
- "1": {
- "options": {
- "0": {
- "question": "Erstellt von {search}"
- }
- }
- },
- "2": {
- "options": {
- "0": {
- "question": "Nicht erstellt von {search}"
- }
- }
- }
- },
- "name": "Schwerpunkte von Änderungssätzen",
- "tagRenderings": {
- "contributor": {
- "render": "Änderung wurde von {_last_edit:contributor} gemacht"
- },
- "render_id": {
- "render": "Änderung {id} "
- },
- "theme": {
- "mappings": {
- "0": {
- "then": "Änderung mit inoffiziellem Thema {theme} "
- }
- },
- "render": "Änderung mit Thema {theme} "
- }
- },
- "title": {
- "render": "Änderungen für {theme}"
- }
- },
- "1": {
- "override": {
- "tagRenderings": {
- "link_to_more": {
- "render": "Weitere Statistiken finden Sie hier "
- }
- }
- }
- }
- },
- "shortDescription": "Zeigt Änderungen von MapComplete",
- "title": "Änderungen mit MapComplete"
- },
"maps": {
"description": "Auf dieser Karte findest du alle Karten, die OpenStreetMap kennt - typischerweise eine große Karte auf einer Informationstafel, die das Gebiet, die Stadt oder die Region zeigt, z.B. eine touristische Karte auf der Rückseite einer Plakatwand, eine Karte eines Naturschutzgebietes, eine Karte der Radwegenetze in der Region, ...) Wenn eine Karte fehlt, können Sie diese leicht auf OpenStreetMap kartieren.",
"shortDescription": "Dieses Thema zeigt alle (touristischen) Karten, die OpenStreetMap kennt",
diff --git a/langs/themes/en.json b/langs/themes/en.json
index 154430e9b..2c2503832 100644
--- a/langs/themes/en.json
+++ b/langs/themes/en.json
@@ -269,138 +269,54 @@
"climbing": {
"description": "On this map you will find various climbing opportunities such as climbing gyms, bouldering halls and rocks in nature.",
"descriptionTail": "The climbing map was originally made by Christian Neumann . Please get in touch if you have feedback or questions.
The project uses data of the OpenStreetMap project.
",
- "overrideAll": {
- "tagRenderings+": {
- "0": {
- "question": "Is there a (unofficial) website with more informations (e.g. topos)?"
- },
- "1": {
- "mappings": {
+ "layers": {
+ "0": {
+ "override": {
+ "tagRenderings+": {
"0": {
- "then": "The containing feature states that this is publicly accessible {_embedding_feature:access:description}"
+ "mappings": {
+ "0": {
+ "then": "The containing feature states that this is publicly accessible {_embedding_feature:access:description}"
+ },
+ "1": {
+ "then": "The containing feature states that a permit is needed to access {_embedding_feature:access:description}"
+ },
+ "2": {
+ "then": "The containing feature states that this is only accessible to customers {_embedding_feature:access:description}"
+ },
+ "3": {
+ "then": "The containing feature states that this is only accessible to club members {_embedding_feature:access:description}"
+ }
+ }
},
"1": {
- "then": "The containing feature states that a permit is needed to access {_embedding_feature:access:description}"
- },
- "2": {
- "then": "The containing feature states that this is only accessible to customers {_embedding_feature:access:description}"
- },
- "3": {
- "then": "The containing feature states that this is only accessible to club members {_embedding_feature:access:description}"
- }
- }
- },
- "2": {
- "mappings": {
- "0": {
- "then": "Publicly accessible to anyone"
- },
- "1": {
- "then": "You need a permit to access here"
- },
- "2": {
- "then": "Only customers"
- },
- "3": {
- "then": "Only club members"
+ "mappings": {
+ "0": {
+ "then": "Publicly accessible to anyone"
+ },
+ "1": {
+ "then": "You need a permit to access here"
+ },
+ "2": {
+ "then": "Only customers"
+ },
+ "3": {
+ "then": "Only club members"
+ }
+ },
+ "question": "Who can access here?"
}
},
- "question": "Who can access here?"
- },
- "4": {
- "question": "What is the (average) length of the routes in meters?",
- "render": "The routes are {canonical(climbing:length)} long on average"
- },
- "5": {
- "question": "What is the grade of the easiest route here, according to the french classification system?",
- "render": "The lowest grade is {climbing:grade:french:min} according to the french/belgian system"
- },
- "6": {
- "question": "What is the highest grade route here, according to the french classification system?",
- "render": "The highest grade is {climbing:grade:french:max} according to the french/belgian system"
- },
- "7": {
- "mappings": {
+ "units+": {
"0": {
- "then": "Bouldering is possible here"
- },
- "1": {
- "then": "Bouldering is not possible here"
- },
- "2": {
- "then": "Bouldering is possible, allthough there are only a few routes"
- },
- "3": {
- "then": "There are {climbing:boulder} boulder routes"
- }
- },
- "question": "Is bouldering possible here?"
- },
- "8": {
- "mappings": {
- "0": {
- "then": "Toprope climbing is possible here"
- },
- "1": {
- "then": "Toprope climbing is not possible here"
- },
- "2": {
- "then": "There are {climbing:toprope} toprope routes"
- }
- },
- "question": "Is toprope climbing possible here?"
- },
- "9": {
- "mappings": {
- "0": {
- "then": "Sport climbing is possible here"
- },
- "1": {
- "then": "Sport climbing is not possible here"
- },
- "2": {
- "then": "There are {climbing:sport} sport climbing routes"
- }
- },
- "question": "Is sport climbing possible here on fixed anchors?"
- },
- "10": {
- "mappings": {
- "0": {
- "then": "Traditional climbing is possible here"
- },
- "1": {
- "then": "Traditional climbing is not possible here"
- },
- "2": {
- "then": "There are {climbing:traditional} traditional climbing routes"
- }
- },
- "question": "Is traditional climbing possible here (using own gear e.g. chocks)?"
- },
- "11": {
- "mappings": {
- "0": {
- "then": "There is a speed climbing wall"
- },
- "1": {
- "then": "There is no speed climbing wall"
- },
- "2": {
- "then": "There are {climbing:speed} speed climbing walls"
- }
- },
- "question": "Is there a speed climbing wall?"
- }
- },
- "units+": {
- "0": {
- "applicableUnits": {
- "0": {
- "human": " meter"
- },
- "1": {
- "human": " feet"
+ "applicableUnits": {
+ "0": {
+ "human": " meter"
+ },
+ "1": {
+ "human": " feet"
+ }
+ }
}
}
}
diff --git a/langs/themes/eo.json b/langs/themes/eo.json
index 4912597b3..ba4794e65 100644
--- a/langs/themes/eo.json
+++ b/langs/themes/eo.json
@@ -1,14 +1,18 @@
{
"climbing": {
- "overrideAll": {
- "units+": {
- "0": {
- "applicableUnits": {
+ "layers": {
+ "0": {
+ "override": {
+ "units+": {
"0": {
- "human": " metro"
- },
- "1": {
- "human": " futo"
+ "applicableUnits": {
+ "0": {
+ "human": " metro"
+ },
+ "1": {
+ "human": " futo"
+ }
+ }
}
}
}
diff --git a/langs/themes/fr.json b/langs/themes/fr.json
index eeb226c08..b7de97d2a 100644
--- a/langs/themes/fr.json
+++ b/langs/themes/fr.json
@@ -269,106 +269,54 @@
"climbing": {
"description": "Cette carte indique les sites d’escalades comme les salles d’escalade ou les sites naturels.",
"descriptionTail": "La carte des sites d'escalade a été créée par Christian Neumann . Merci de le contacter pour des avis ou des questions.Ce projet utilise les données OpenStreetMap .
",
- "overrideAll": {
- "tagRenderings+": {
- "0": {
- "question": "Existe-t’il un site avec plus d’informations (ex : topographie) ?"
- },
- "1": {
- "mappings": {
+ "layers": {
+ "0": {
+ "override": {
+ "tagRenderings+": {
"0": {
- "then": "L’élément englobant indique un accès libre {_embedding_feature:access:description}"
+ "mappings": {
+ "0": {
+ "then": "L’élément englobant indique un accès libre {_embedding_feature:access:description}"
+ },
+ "1": {
+ "then": "L’élément englobant indique qu’ une autorisation d’accès est nécessaire {_embedding_feature:access:description}"
+ },
+ "2": {
+ "then": "L’élément englobant indique que l’accès est réservés aux clients {_embedding_feature:access:description}"
+ },
+ "3": {
+ "then": "L’élément englobant indique que l’accès est réservé aux membres {_embedding_feature:access:description}"
+ }
+ }
},
"1": {
- "then": "L’élément englobant indique qu’ une autorisation d’accès est nécessaire {_embedding_feature:access:description}"
- },
- "2": {
- "then": "L’élément englobant indique que l’accès est réservés aux clients {_embedding_feature:access:description}"
- },
- "3": {
- "then": "L’élément englobant indique que l’accès est réservé aux membres {_embedding_feature:access:description}"
- }
- }
- },
- "2": {
- "mappings": {
- "0": {
- "then": "Libre d’accès"
- },
- "1": {
- "then": "Une autorisation est nécessaire"
- },
- "2": {
- "then": "Réservé aux clients"
- },
- "3": {
- "then": "Réservé aux membres"
+ "mappings": {
+ "0": {
+ "then": "Libre d’accès"
+ },
+ "1": {
+ "then": "Une autorisation est nécessaire"
+ },
+ "2": {
+ "then": "Réservé aux clients"
+ },
+ "3": {
+ "then": "Réservé aux membres"
+ }
+ },
+ "question": "Qui peut y accéder ?"
}
},
- "question": "Qui peut y accéder ?"
- },
- "4": {
- "question": "Quelle est la longueur moyenne des voies en mètres ?",
- "render": "Les voies font {canonical(climbing:length)} de long en moyenne"
- },
- "5": {
- "question": "Quel est le niveau de la voie la plus simple selon la classification franco-belge ?",
- "render": "La difficulté minimale est {climbing:grade:french:min} selon la classification franco-belge"
- },
- "6": {
- "question": "Quel est le niveau de la voie la plus difficile selon la classification franco-belge ?",
- "render": "La difficulté maximale est {climbing:grade:french:max} selon la classification franco-belge"
- },
- "7": {
- "mappings": {
+ "units+": {
"0": {
- "then": "L’escalade de bloc est possible"
- },
- "1": {
- "then": "L’escalade de bloc n’est pas possible"
- },
- "2": {
- "then": "L’escalade de bloc est possible sur des voies précises"
- },
- "3": {
- "then": "Il y a {climbing:boulder} voies d’escalade de bloc"
- }
- },
- "question": "L’escalade de bloc est-elle possible ici ?"
- },
- "8": {
- "mappings": {
- "0": {
- "then": "L’escalade à la moulinette est possible"
- },
- "1": {
- "then": "L’escalade à la moulinette n’est pas possible"
- },
- "2": {
- "then": "{climbing:toprope} voies sont équipées de moulinettes"
- }
- },
- "question": "Est-il possible d’escalader à la moulinette ?"
- },
- "9": {
- "mappings": {
- "0": {
- "then": "De l’escalade est possible ici"
- },
- "1": {
- "then": "L’escalade est impossible ici"
- }
- }
- }
- },
- "units+": {
- "0": {
- "applicableUnits": {
- "0": {
- "human": " mètres"
- },
- "1": {
- "human": " pieds"
+ "applicableUnits": {
+ "0": {
+ "human": " mètres"
+ },
+ "1": {
+ "human": " pieds"
+ }
+ }
}
}
}
diff --git a/langs/themes/hu.json b/langs/themes/hu.json
index c7e3fc42d..40bce9c7b 100644
--- a/langs/themes/hu.json
+++ b/langs/themes/hu.json
@@ -93,20 +93,6 @@
"climbing": {
"description": "Ezen a térképen különböző mászási lehetőségeket talál, például falmászótermeket, bouldertermeket és sziklákat a természetben.",
"descriptionTail": "A mászótérképet eredetileg Christian Neumann készítette. Ha észrevétele vagy kérdése van, kérjük, vele lépjen kapcsolatba . A projekt az OpenStreetMap adatait használja
",
- "overrideAll": {
- "tagRenderings+": {
- "9": {
- "mappings": {
- "0": {
- "then": "Itt lehetőség van sportmászásra"
- },
- "1": {
- "then": "Itt nincs lehetőség sportmászásra"
- }
- }
- }
- }
- },
"title": "Mászótérkép"
},
"cycle_infra": {
diff --git a/langs/themes/it.json b/langs/themes/it.json
index b39d5b450..f9518d0d4 100644
--- a/langs/themes/it.json
+++ b/langs/themes/it.json
@@ -263,138 +263,54 @@
"climbing": {
"description": "In questa cartina puoi trovare vari luoghi per arrampicata come ad esempio palestre di arrampicata, sale di pratica e rocce naturali.",
"descriptionTail": "La cartina di arrampicata è stata originariamente creata da Christian Neumann . Si prega di scrivere qua se si hanno commenti o domande da fare.Il progetto usa i dati del progetto OpenStreetMap .
",
- "overrideAll": {
- "tagRenderings+": {
- "0": {
- "question": "C’è un sito web (anche non ufficiale) con qualche informazione in più (ad es. topografie)?"
- },
- "1": {
- "mappings": {
+ "layers": {
+ "0": {
+ "override": {
+ "tagRenderings+": {
"0": {
- "then": "L’ elemento in cui è contenuto indica che è pubblicamente accessibile {_embedding_feature:access:description}"
+ "mappings": {
+ "0": {
+ "then": "L’ elemento in cui è contenuto indica che è pubblicamente accessibile {_embedding_feature:access:description}"
+ },
+ "1": {
+ "then": "L’elemento che lo contiene indica che è richiesto un’autorizzazione per accedervi {_embedding_feature:access:description}"
+ },
+ "2": {
+ "then": "L’ elemento che lo contiene indica che è accessibile solo ai clienti {_embedding_feature:access:description}"
+ },
+ "3": {
+ "then": "L’ elemento che lo contiene indica che è accessibile solamente ai membri del club {_embedding_feature:access:description}"
+ }
+ }
},
"1": {
- "then": "L’elemento che lo contiene indica che è richiesto un’autorizzazione per accedervi {_embedding_feature:access:description}"
- },
- "2": {
- "then": "L’ elemento che lo contiene indica che è accessibile solo ai clienti {_embedding_feature:access:description}"
- },
- "3": {
- "then": "L’ elemento che lo contiene indica che è accessibile solamente ai membri del club {_embedding_feature:access:description}"
- }
- }
- },
- "2": {
- "mappings": {
- "0": {
- "then": "Pubblicamente accessibile a chiunque"
- },
- "1": {
- "then": "È necessario avere un’autorizzazione per entrare"
- },
- "2": {
- "then": "Riservato ai clienti"
- },
- "3": {
- "then": "Riservato ai membri del club"
+ "mappings": {
+ "0": {
+ "then": "Pubblicamente accessibile a chiunque"
+ },
+ "1": {
+ "then": "È necessario avere un’autorizzazione per entrare"
+ },
+ "2": {
+ "then": "Riservato ai clienti"
+ },
+ "3": {
+ "then": "Riservato ai membri del club"
+ }
+ },
+ "question": "Chi può accedervi?"
}
},
- "question": "Chi può accedervi?"
- },
- "4": {
- "question": "Quale è la lunghezza (media) delle vie in metri?",
- "render": "Le vie sono lunghe mediamente {canonical(climbing:length)} "
- },
- "5": {
- "question": "Qual è il livello della via più facile qua, secondo il sistema di classificazione francese?",
- "render": "Il minimo livello di difficoltà è {climbing:grade:french:min} secondo il sistema francese/belga"
- },
- "6": {
- "question": "Qual è il livello della via più difficile qua, secondo il sistema di classificazione francese?",
- "render": "Il massimo livello di difficoltà è {climbing:grade:french:max} secondo il sistema francese/belga"
- },
- "7": {
- "mappings": {
+ "units+": {
"0": {
- "then": "L’arrampicata su massi è possibile qua"
- },
- "1": {
- "then": "L’arrampicata su massi non è possibile qua"
- },
- "2": {
- "then": "L’arrampicata su massi è possibile anche se su poche vie"
- },
- "3": {
- "then": "Sono presenti {climbing:boulder} vie di arrampicata su massi"
- }
- },
- "question": "È possibile praticare ‘bouldering’ qua?"
- },
- "8": {
- "mappings": {
- "0": {
- "then": "È possibile arrampicarsi con moulinette qua"
- },
- "1": {
- "then": "Non è possibile arrampicarsi con moulinette qua"
- },
- "2": {
- "then": "Sono presenti {climbing:toprope} vie con moulinette"
- }
- },
- "question": "È possibile arrampicarsi con la corda dall’alto qua?"
- },
- "9": {
- "mappings": {
- "0": {
- "then": "L’arrampicata sportiva è possibile qua"
- },
- "1": {
- "then": "L’arrampicata sportiva non è possibile qua"
- },
- "2": {
- "then": "Sono presenti {climbing:sport} vie di arrampicata sportiva"
- }
- },
- "question": "È possibile arrampicarsi qua con ancoraggi fissi?"
- },
- "10": {
- "mappings": {
- "0": {
- "then": "L’arrampicata tradizionale è possibile qua"
- },
- "1": {
- "then": "L’arrampicata tradizionale non è possibile qua"
- },
- "2": {
- "then": "Sono presenti {climbing:traditional} vie di arrampicata tradizionale"
- }
- },
- "question": "È possibile arrampicarsi in maniera tradizionale qua (usando attrezzi propri, ad es. dadi)?"
- },
- "11": {
- "mappings": {
- "0": {
- "then": "È presente una parete per l’arrampicata di velocità"
- },
- "1": {
- "then": "Non è presente una parete per l’arrampicata di velocità"
- },
- "2": {
- "then": "Sono presenti {climbing:speed} pareti per l’arrampicata di velocità"
- }
- },
- "question": "È presente una prete per l’arrampicata di velocità?"
- }
- },
- "units+": {
- "0": {
- "applicableUnits": {
- "0": {
- "human": " metri"
- },
- "1": {
- "human": " piedi"
+ "applicableUnits": {
+ "0": {
+ "human": " metri"
+ },
+ "1": {
+ "human": " piedi"
+ }
+ }
}
}
}
diff --git a/langs/themes/ja.json b/langs/themes/ja.json
index 35b0d41ef..fc80489e4 100644
--- a/langs/themes/ja.json
+++ b/langs/themes/ja.json
@@ -255,98 +255,6 @@
"climbing": {
"description": "この地図には、自然の中のクライミングジム、ボルダリングホール、岩など、さまざまなクライミングの機会があります。",
"descriptionTail": "登山地図はもともと Christian Neumann によって作成されたものです。フィードバックや質問がありましたら、ご連絡 ください。このプロジェクトでは、OpenStreetMap プロジェクトのデータを使用します。
",
- "overrideAll": {
- "tagRenderings+": {
- "0": {
- "question": "もっと情報のある(非公式の)ウェブサイトはありますか(例えば、topos)?"
- },
- "4": {
- "question": "ルートの(平均)長さはメートル単位でいくつですか?",
- "render": "ルートの長さは平均で{canonical(climbing:length)} です"
- },
- "5": {
- "question": "ここで一番簡単なルートのレベルは、フランスのランク評価システムで何ですか?",
- "render": "フランス/ベルギーのランク評価システムでは、最小の難易度は{climbing:grade:french:min}です"
- },
- "6": {
- "question": "フランスのランク評価によると、ここで一番難しいルートのレベルはどれくらいですか?",
- "render": "フランス/ベルギーのランク評価システムでは、最大の難易度は{climbing:grade:french:max}です"
- },
- "7": {
- "mappings": {
- "0": {
- "then": "ボルダリングはここで可能です"
- },
- "1": {
- "then": "ここではボルダリングはできません"
- },
- "2": {
- "then": "ボルダリングは可能ですが、少しのルートしかありません"
- },
- "3": {
- "then": "{climbing:boulder} ボルダールートがある"
- }
- },
- "question": "ここでボルダリングはできますか?"
- },
- "8": {
- "mappings": {
- "0": {
- "then": "ここでToprope登坂ができます"
- },
- "1": {
- "then": "ここではToprope登坂はできません"
- },
- "2": {
- "then": "{climbing:toprope} 登坂ルートがある"
- }
- },
- "question": "ここでtoprope登坂はできますか?"
- },
- "9": {
- "mappings": {
- "0": {
- "then": "ここでスポーツクライミングができます"
- },
- "1": {
- "then": "ここではスポーツクライミングはできません"
- },
- "2": {
- "then": "スポーツクライミングの {climbing:sport} ルートがある"
- }
- },
- "question": "ここでは固定アンカー式のスポーツクライミングはできますか?"
- },
- "10": {
- "mappings": {
- "0": {
- "then": "ここでは伝統的な登山が可能です"
- },
- "1": {
- "then": "伝統的な登山はここではできない"
- },
- "2": {
- "then": "{climbing:traditional} の伝統的な登山ルートがある"
- }
- },
- "question": "伝統的な登山はここで可能ですか(例えば、チョックのような独自のギアを使用して)?"
- },
- "11": {
- "mappings": {
- "0": {
- "then": "スピードクライミングウォールがある"
- },
- "1": {
- "then": "スピードクライミングウォールがない"
- },
- "2": {
- "then": "{climbing:speed} のスピードクライミングウォールがある"
- }
- },
- "question": "スピードクライミングウォールはありますか?"
- }
- }
- },
"title": "登山地図を開く"
},
"cyclestreets": {
diff --git a/langs/themes/nb_NO.json b/langs/themes/nb_NO.json
index 12fd79fcc..42c134b0a 100644
--- a/langs/themes/nb_NO.json
+++ b/langs/themes/nb_NO.json
@@ -69,21 +69,6 @@
"title": "Ladestasjoner"
},
"climbing": {
- "overrideAll": {
- "tagRenderings+": {
- "7": {
- "mappings": {
- "0": {
- "then": "Buldring er mulig her"
- },
- "1": {
- "then": "Buldring er ikke mulig her"
- }
- },
- "question": "Er buldring mulig her?"
- }
- }
- },
"title": "Åpent klatrekart"
},
"cycle_infra": {
diff --git a/langs/themes/nl.json b/langs/themes/nl.json
index c7cee2f37..f29c2d8a4 100644
--- a/langs/themes/nl.json
+++ b/langs/themes/nl.json
@@ -289,115 +289,31 @@
"climbing": {
"description": "Op deze kaart vind je verschillende klimgelegenheden, zoals klimzalen, bolderzalen en klimmen in de natuur",
"descriptionTail": "De klimkaart is oorspronkelijk gemaakt door Christian Neumann op kletterspots.de .",
- "overrideAll": {
- "tagRenderings+": {
- "0": {
- "question": "Is er een (onofficiële) website met meer informatie (b.v. met topos)?"
- },
- "1": {
- "mappings": {
+ "layers": {
+ "0": {
+ "override": {
+ "tagRenderings+": {
"0": {
- "then": "Een omvattend element geeft aan dat dit publiek toegangkelijk is {_embedding_feature:access:description}"
- },
- "1": {
- "then": "Een omvattend element geeft aan dat een toelating nodig is om hier te klimmen {_embedding_feature:access:description}"
- }
- }
- },
- "4": {
- "question": "Wat is de (gemiddelde) lengte van de klimroutes, in meter?",
- "render": "De klimroutes zijn gemiddeld {canonical(climbing:length)} lang"
- },
- "5": {
- "question": "Wat is het niveau van de makkelijkste route, volgens het Franse classificatiesysteem?",
- "render": "De minimale klimmoeilijkheid is {climbing:grade:french:min} volgens het Franse/Belgische systeem"
- },
- "6": {
- "question": "Wat is het niveau van de moeilijkste route, volgens het Franse classificatiesysteem?",
- "render": "De maximale klimmoeilijkheid is {climbing:grade:french:max} volgens het Franse/Belgische systeem"
- },
- "7": {
- "mappings": {
- "0": {
- "then": "Bolderen kan hier"
- },
- "1": {
- "then": "Bolderen kan hier niet"
- },
- "2": {
- "then": "Bolderen kan hier, maar er zijn niet zoveel routes"
- },
- "3": {
- "then": "Er zijn hier {climbing:boulder} bolderroutes"
+ "mappings": {
+ "0": {
+ "then": "Een omvattend element geeft aan dat dit publiek toegangkelijk is {_embedding_feature:access:description}"
+ },
+ "1": {
+ "then": "Een omvattend element geeft aan dat een toelating nodig is om hier te klimmen {_embedding_feature:access:description}"
+ }
+ }
}
},
- "question": "Is het mogelijk om hier te bolderen?"
- },
- "8": {
- "mappings": {
+ "units+": {
"0": {
- "then": "Toprope-klimmen kan hier"
- },
- "1": {
- "then": "Toprope-klimmen kan hier niet"
- },
- "2": {
- "then": "Er zijn hier {climbing:toprope} toprope routes"
- }
- },
- "question": "Is het mogelijk om hier te toprope-klimmen?"
- },
- "9": {
- "mappings": {
- "0": {
- "then": "Sportklimmen/voorklimmen kan hier"
- },
- "1": {
- "then": "Sportklimmen/voorklimmen kan hier niet"
- },
- "2": {
- "then": "Er zijn hier {climbing:sport} sportklimroutes/voorklimroutes"
- }
- },
- "question": "Is het mogelijk om hier te sportklimmen/voorklimmen op reeds aangebrachte haken?"
- },
- "10": {
- "mappings": {
- "0": {
- "then": "Traditioneel klimmen kan hier"
- },
- "1": {
- "then": "Traditioneel klimmen kan hier niet"
- },
- "2": {
- "then": "Er zijn hier {climbing:traditional} traditionele klimroutes"
- }
- },
- "question": "Is het mogelijk om hier traditioneel te klimmen? (Dit is klimmen met klemblokjes en friends) "
- },
- "11": {
- "mappings": {
- "0": {
- "then": "Er is een snelklimmuur voor speed climbing"
- },
- "1": {
- "then": "Er is geen snelklimmuur voor speed climbing"
- },
- "2": {
- "then": "Er zijn hier {climbing:speed} snelklimmuren"
- }
- },
- "question": "Is er een snelklimmuur (speed climbing)?"
- }
- },
- "units+": {
- "0": {
- "applicableUnits": {
- "0": {
- "human": " meter"
- },
- "1": {
- "human": " voet"
+ "applicableUnits": {
+ "0": {
+ "human": " meter"
+ },
+ "1": {
+ "human": " voet"
+ }
+ }
}
}
}
diff --git a/langs/themes/ru.json b/langs/themes/ru.json
index 07e2e7290..800a312d1 100644
--- a/langs/themes/ru.json
+++ b/langs/themes/ru.json
@@ -227,34 +227,25 @@
"climbing": {
"description": "На этой карте вы найдете различные возможности для скалолазания, такие как скалодромы, залы для боулдеринга и скалы на природе.",
"descriptionTail": "Создатель карты скалолазания — Christian Neumann . Пожалуйста, пишите если у вас есть отзыв или вопросы.Проект использует данные OpenStreetMap .
",
- "overrideAll": {
- "tagRenderings+": {
- "0": {
- "question": "Есть ли (неофициальный) веб-сайт с более подробной информацией (напр., topos)?"
- },
- "2": {
- "mappings": {
- "3": {
- "then": "Только членам клуба"
- }
- }
- },
- "9": {
- "mappings": {
- "0": {
- "then": "Здесь можно заняться спортивным скалолазанием"
- },
+ "layers": {
+ "0": {
+ "override": {
+ "tagRenderings+": {
"1": {
- "then": "Спортивное скалолазание здесь невозможно"
+ "mappings": {
+ "3": {
+ "then": "Только членам клуба"
+ }
+ }
}
- }
- }
- },
- "units+": {
- "0": {
- "applicableUnits": {
+ },
+ "units+": {
"0": {
- "human": " метр"
+ "applicableUnits": {
+ "0": {
+ "human": " метр"
+ }
+ }
}
}
}
diff --git a/package.json b/package.json
index 1488ede86..6b4ef97b7 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
"main": "index.js",
"scripts": {
"start": "npm run generate:layeroverview && npm run strt",
- "strt": "export NODE_OPTIONS=--max_old_space_size=8364 && parcel serve *.html UI/** Logic/** assets/*.json assets/svg/* assets/generated/* assets/layers/*/*.svg assets/layers/*/*/*/*.svg assets/layers/*/*.jpg assets/layers/*/*.png assets/layers/*/*.css assets/tagRenderings/*.json assets/themes/*/*.svg assets/themes/*/*.ttf assets/themes/*/*/*.ttf aassets/themes/*/*.otf assets/themes/*/*/*.otf ssets/themes/*/*.css assets/themes/*/*.jpg assets/themes/*/*.png vendor/* vendor/*/*",
+ "strt": "export NODE_OPTIONS=--max_old_space_size=8364 && parcel serve *.html UI/** Logic/** assets/*.json assets/svg/* assets/generated/* assets/layers/*/*.svg assets/layers/*/*/*/*.svg assets/layers/*/*.jpg assets/layers/*/*.png assets/layers/*/*.css assets/tagRenderings/*.json assets/themes/*/*.svg assets/themes/*/*.ttf assets/themes/*/*/*.ttf assets/themes/*/*.otf assets/themes/*/*/*.otf assets/themes/*/*.css assets/themes/*/*.jpg assets/themes/*/*.woff assets/themes/*/*.png vendor/* vendor/*/*",
"strttest": "export NODE_OPTIONS=--max_old_space_size=8364 && parcel serve test.html",
"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",
diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts
index c9a17230f..9dab50a3b 100644
--- a/scripts/generateLayerOverview.ts
+++ b/scripts/generateLayerOverview.ts
@@ -169,6 +169,9 @@ class LayerOverviewUtils {
}
this.checkAllSvgs()
+
+ const green = s => '\x1b[92m' + s + '\x1b[0m'
+ console.log(green("All done!"))
}
private buildLayerIndex(knownImagePaths: Set): Map {
@@ -216,16 +219,22 @@ class LayerOverviewUtils {
const themePath = themeInfo.path
new PrevalidateTheme().convertStrict(themeFile, themePath)
- themeFile = new PrepareTheme(convertState).convertStrict(themeFile, themePath)
-
- if(knownImagePaths === undefined){
- throw "Could not load known images/licenses"
+ try{
+
+ themeFile = new PrepareTheme(convertState).convertStrict(themeFile, themePath)
+
+ if(knownImagePaths === undefined){
+ throw "Could not load known images/licenses"
+ }
+ new ValidateThemeAndLayers(knownImagePaths, themePath, true, convertState.tagRenderings)
+ .convertStrict(themeFile, themePath)
+
+ this.writeTheme(themeFile)
+ fixed.set(themeFile.id, themeFile)
+ }catch(e){
+ console.error("ERROR: could not prepare theme "+themePath+" due to "+e)
+ throw e;
}
- new ValidateThemeAndLayers(knownImagePaths, themePath, true, convertState.tagRenderings)
- .convertStrict(themeFile, themePath)
-
- this.writeTheme(themeFile)
- fixed.set(themeFile.id, themeFile)
}
this.writeSmallOverview(Array.from(fixed.values()).map(t => {
diff --git a/test.ts b/test.ts
index 9a6b02f97..4640df0ca 100644
--- a/test.ts
+++ b/test.ts
@@ -1,31 +1,17 @@
-import Combine from "./UI/Base/Combine";
-import ValidatedTextField from "./UI/Input/ValidatedTextField";
-import Title from "./UI/Base/Title";
-import {FixedUiElement} from "./UI/Base/FixedUiElement";
import {VariableUiElement} from "./UI/Base/VariableUIElement";
import {UIEventSource} from "./Logic/UIEventSource";
-import {Translation} from "./UI/i18n/Translation";
+import Wikidata from "./Logic/Web/Wikidata";
+import Combine from "./UI/Base/Combine";
+import {FixedUiElement} from "./UI/Base/FixedUiElement";
-new Combine(
- ValidatedTextField.AvailableTypes().map(key => {
- let inp;
- const feedback = new UIEventSource(undefined)
- try {
- inp = ValidatedTextField.ForType(key).ConstructInputElement({
- feedback,
- country: () => "be"
- });
- } catch (e) {
- console.error(e)
- inp = new FixedUiElement(e).SetClass("alert")
- }
-
- return new Combine([
- new Title(key),
- inp,
- new VariableUiElement(inp.GetValue()),
- new VariableUiElement(feedback.map(v => v?.SetClass("alert")))
- ]);
- }
- )
-).AttachTo("maindiv")
\ No newline at end of file
+const result = UIEventSource.FromPromise(
+ Wikidata.searchAdvanced("WOlf", {
+ lang: "nl",
+ maxCount: 100,
+ instanceOf: 5
+ })
+)
+result.addCallbackAndRunD(r => console.log(r))
+new VariableUiElement(result.map(items =>new Combine( (items??[])?.map(i =>
+ new FixedUiElement(JSON.stringify(i, null, " ")).SetClass("p-4 block")
+)) )).SetClass("flex flex-col").AttachTo("maindiv")
\ No newline at end of file