Merge branch 'develop'

# Conflicts:
#	assets/themes/climbing/climbing.json
#	assets/themes/mapcomplete-changes/mapcomplete-changes.json
#	css/index-tailwind-output.css
This commit is contained in:
pietervdvn 2022-04-29 23:35:11 +02:00
commit ecba715265
90 changed files with 2564 additions and 2545 deletions

View file

@ -20,6 +20,11 @@ export class AllKnownLayouts {
public static AllPublicLayers() {
const allLayers: LayerConfig[] = []
const seendIds = new Set<string>()
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
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

BIN
Docs/Misc/ImportAPoint.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
Docs/Screenshots/AED.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
Docs/Screenshots/AddNew.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View file

@ -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

View file

@ -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;
}

View file

@ -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<GeoCodeResult[]> {
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)
}
}

View file

@ -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<any> {
public closeNote(id: number | string, text?: string): Promise<void> {
let textSuffix = ""
if ((text ?? "") !== "") {
textSuffix = "?text=" + encodeURIComponent(text)
@ -249,7 +248,7 @@ export class OsmConnection {
}
public reopenNote(id: number | string, text?: string): Promise<any> {
public reopenNote(id: number | string, text?: string): Promise<void> {
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<any> {
public addCommentToNode(id: number | string, text: string): Promise<void> {
if (this._dryRun.data) {
console.warn("Dryrun enabled - not actually adding comment ", text, "to note ", id)
return new Promise((ok, error) => {

View file

@ -69,7 +69,7 @@ export abstract class OsmObject {
return rawData.elements[0].tags
}
static async DownloadObjectAsync(id: string): Promise<OsmObject> {
static async DownloadObjectAsync(id: string): Promise<OsmObject | undefined> {
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

View file

@ -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 {

View file

@ -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<any>;
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;
}

View file

@ -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<string, UIEventSource<{ success: WikidataResponse } | { error: any }>>()
@ -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<WikidataResponse[]> {
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 => <WikidataResponse>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 => <WikidataResponse>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)
}
}
}

View file

@ -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) {

View file

@ -216,13 +216,18 @@ export class Fuse<T> extends DesugaringStep<T> {
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 {

View file

@ -330,12 +330,19 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
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<string, LayerConfigJson>, themeId: string): LayerConfigJson[] {
const dependenciesToAdd: LayerConfigJson[] = []
private static CalculateDependencies(alreadyLoaded: LayerConfigJson[], allKnownLayers: Map<string, LayerConfigJson>, themeId: string):
{config: LayerConfigJson, reason: string}[] {
const dependenciesToAdd: {config: LayerConfigJson, reason: string}[] = []
const loadedLayerIds: Set<string> = new Set<string>(alreadyLoaded.map(l => l.id));
// Verify cross-dependencies
@ -361,35 +368,39 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
}
// 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<LayoutConfigJson> {
})
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<LayoutConfigJson> {
}) {
super(
"Fully prepares and expands a theme",
new AddContextToTransltionsInLayout(),
new PreparePersonalTheme(state),
new WarnForUnsubstitutedLayersInTheme(),

View file

@ -217,12 +217,17 @@ class MiscThemeChecks extends DesugaringStep<LayoutConfigJson>{
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<LayoutConfigJson> {
constructor() {
super("Various consistency checks on the raw JSON",
new OverrideShadowingCheck(),
new MiscThemeChecks()
new MiscThemeChecks(),
new OverrideShadowingCheck()
);
}

View file

@ -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
})

View file

@ -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.

View file

@ -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,
/**

View file

@ -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<string, string[]>, dependencies: {
public GenerateDocumentation(usedInThemes: string[], layerIsNeededBy?: Map<string, string[]>, 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")

View file

@ -15,8 +15,8 @@ import {VariableUiElement} from "../../UI/Base/VariableUIElement";
export default class PointRenderingConfig extends WithContextLoader {
private static readonly allowed_location_codes = new Set<string>(["point", "centroid", "start", "end"])
public readonly location: Set<"point" | "centroid" | "start" | "end" | string>
private static readonly allowed_location_codes = new Set<string>(["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 }[];

46
UI/AllTagsPanel.ts Normal file
View file

@ -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<any>, 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 = "<b>empty string</b>"
}
parts.push([key, v ?? "<b>undefined</b>"]);
}
for (const key of calculatedTags) {
const value = tags[key]
if (value === undefined) {
continue
}
let type = "";
if (typeof value !== "string") {
type = " <i>" + (typeof value) + "</i>"
}
parts.push(["<i>" + key + "</i>", value])
}
return new Table(
["key", "value"],
parts
)
.SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;").SetClass("zebra-table")
}))
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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<FilteredLayer[]>,
@ -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<boolean>}) {
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;";

View file

@ -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);

View file

@ -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")
}

View file

@ -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
)
}

View file

@ -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
)

View file

@ -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 {

View file

@ -13,7 +13,7 @@ export default class Toggle extends VariableUiElement {
constructor(showEnabled: string | BaseUIElement, showDisabled: string | BaseUIElement, isEnabled: UIEventSource<boolean> = new UIEventSource<boolean>(false)) {
super(
isEnabled.map(isEnabled => isEnabled ? showEnabled : showDisabled)
isEnabled?.map(isEnabled => isEnabled ? showEnabled : showDisabled)
);
this.isEnabled = isEnabled
}

View file

@ -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 = <string>inputHelperOptions.feature?.properties[searchKey]?.toLowerCase()
let searchFor = <string>(inputHelperOptions.feature?.properties[searchKey]?.toLowerCase() ?? "")
const options = args[1]
const options: any = args[1]
if (searchFor !== undefined && options !== undefined) {
const prefixes = <string[]>options["removePrefixes"]
const postfixes = <string[]>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<string>(searchFor)
searchText: new UIEventSource<string>(searchFor),
instanceOf,
notInstanceOf
})
}
}

View file

@ -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));
}
}

View file

@ -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<any>, 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));
}
}

View file

@ -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<object>, 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<object>, icon?: string, ifnot?: TagsFilter, addExtraTags: Tag[] }[],
applicableUnit: Unit,
tagsSource: UIEventSource<any>,
feedback: UIEventSource<Translation>
@ -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)
}
})
)

View file

@ -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<L.Map>;
private readonly _enablePopups: boolean;
private readonly _features: RenderingMultiPlexerFeatureSource
private readonly _layerToShow: LayerConfig;
private readonly _selectedElement: UIEventSource<any>
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<string, { feature: any, leafletlayer: any }>()
private readonly showDataLayerid: number;
private readonly createPopup: (tags: UIEventSource<any>, 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<any>(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<any>(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<any>(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(`<div style='height: 65vh' id='${id}'>Popup for ${feature.properties.id} ${feature.geometry.type} ${id} is loading</div>`)
const createpopup = this.createPopup;
leafletLayer.on("popupopen", () => {
if (infobox === undefined) {
const tags = this.allElements?.getEventSourceById(feature.properties.id) ?? new UIEventSource<any>(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) {
}
}

View file

@ -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<any>, 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 = "<b>empty string</b>"
}
parts.push([key, v ?? "<b>undefined</b>"]);
}
for (const key of calculatedTags) {
const value = tags[key]
if (value === undefined) {
continue
}
parts.push(["<i>" + key + "</i>", 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 = <WikidataResponse>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;
}

View file

@ -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}\\((.*?)\\)(:.*)?}(.*)`);

View file

@ -17,14 +17,20 @@ export default class WikidataSearchBox extends InputElement<string> {
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private readonly wikidataId: UIEventSource<string>
private readonly searchText: UIEventSource<string>
private readonly instanceOf?: number[];
private readonly notInstanceOf?: number[];
constructor(options?: {
searchText?: UIEventSource<string>,
value?: UIEventSource<string>
value?: UIEventSource<string>,
notInstanceOf?: number[],
instanceOf?: number[]
}) {
super();
this.searchText = options?.searchText
this.wikidataId = options?.value ?? new UIEventSource<string>(undefined);
this.instanceOf = options?.instanceOf
this.notInstanceOf = options?.notInstanceOf
}
GetValue(): UIEventSource<string> {
@ -59,7 +65,9 @@ export default class WikidataSearchBox extends InputElement<string> {
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<string> {
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")

View file

@ -1,300 +1,375 @@
{
"id": "climbing",
"name": {
"nl": "Klimgelegenheden",
"de": "Klettermöglichkeiten",
"en": "Climbing opportunities",
"ja": "登坂教室",
"fr": "Opportunité descalade",
"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é descalade",
"it": "Opportunità di arrampicata"
},
"mappings": [
{
"if": "climbing=crag",
"then": {
"en": "Climbing crag <b>{name}</b>",
"fr": "Mur descalade <b>{name}</b>",
"it": "Muro da arrampicata <b>{name}</b>",
"de": "Klettergarten <b>{name}</b>"
}
},
{
"if": {
"and": [
{
"or": [
"climbing=area",
"climbing=site"
]
},
"name~*"
]
},
"then": {
"en": "Climbing area <b>{name}</b>",
"nl": "Klimsite <b>{name}</b>",
"fr": "Zone descalade <b>{name}</b>",
"de": "Klettergebiet <b>{name}</b>",
"it": "Area di arrampicata <b>{name}</b>"
}
},
{
"if": {
"or": [
"climbing=site",
"climbing=area"
]
},
"then": {
"en": "Climbing site",
"nl": "Klimsite",
"fr": "Site descalade",
"de": "Klettergebiet",
"it": "Sito di arrampicata",
"ca": "Llocs d'escalada"
}
},
{
"if": "name~*",
"then": {
"nl": "Klimgelegenheid <b>{name}</b>",
"en": "Climbing opportunity <b>{name}</b>",
"fr": "Opportunité descalade <b>{name}</b>",
"de": "Klettermöglichkeit <b>{name}</b>",
"it": "Opportunità di arrampicata <b>{name}</b>"
}
}
]
},
"title": null,
"description": {
"nl": "Een klimgelegenheid",
"de": "Eine Klettergelegenheit",
"en": "A climbing opportunity",
"ja": "登坂教室",
"nb_NO": "En klatremulighet",
"fr": "Opportunité descalade",
"it": "Unopportunità 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": "<h3>Length overview</h3>{histogram(_length_hist)}",
"fr": "<h3>Résumé de longueur</h3>{histogram(_length_hist)}",
"de": "<h3>Längenübersicht</h3>{histogram(_length_hist)}",
"it": "<h3>Riassunto della lunghezza</h3>{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-til un site avec plus dinformations (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": "<h3>Grades overview</h3>{histogram(_difficulty_hist)}",
"fr": "<h3>Résumé des difficultés</h3>{histogram(_difficulty_hist)}",
"de": "<h3>Schwierigkeitsübersicht</h3>{histogram(_difficulty_hist)}",
"it": "<h3>Riassunto delle difficoltà</h3>{histogram(_difficulty_hist)}"
"condition": {
"and": [
"leisure!~sports_centre",
"sport=climbing",
"office=",
"club="
]
},
"condition": "_difficulty_hist!~\\[\\]",
"id": "Contained routes hist"
"render": "<a href='{url}' target='_blank'>{url}</a>",
"freeform": {
"key": "url",
"type": "url"
}
},
{
"id": "average_length",
"render": {
"en": "<h3>Contains {_contained_climbing_routes_count} routes</h3> <ul>{_contained_climbing_routes}</ul>",
"fr": "<h3>Contient {_contained_climbing_routes_count} voies</h3> <ul>{_contained_climbing_routes}</ul>",
"it": "<h3>Contiene {_contained_climbing_routes_count} vie</h3> <ul>{_contained_climbing_routes}</ul>",
"de": "<h3> Enthält {_contained_climbing_routes_count} Routen</h3> <ul>{_contained_climbing_routes}</ul>"
},
"condition": "_contained_climbing_routes~*",
"id": "Contained_climbing_routes"
},
{
"render": {
"en": "<strong>{name}</strong>",
"nl": "<strong>{name}</strong>",
"de": "<strong>{name}</strong>",
"ca": "<strong>{name}</strong>",
"fr": "<strong>{name}</strong>",
"id": "<strong>{name}</strong>",
"ru": "<strong>{name}</strong>",
"ja": "<strong>{name}</strong>",
"it": "<strong>{name}</strong>"
"de": "Die Routen sind durchschnittlich <b>{canonical(climbing:length)}</b> lang",
"en": "The routes are <b>{canonical(climbing:length)}</b> long on average",
"nl": "De klimroutes zijn gemiddeld <b>{canonical(climbing:length)}</b> lang",
"ja": "ルートの長さは平均で<b>{canonical(climbing:length)}</b>です",
"fr": "Les voies font <b>{canonical(climbing:length)}</b> de long en moyenne",
"it": "Le vie sono lunghe mediamente <b>{canonical(climbing:length)}</b>"
},
"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": "Lescalade 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": "Lescalade de bloc est possible",
"it": "Larrampicata 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": "Lescalade de bloc nest pas possible",
"it": "Larrampicata 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": "Lescalade de bloc est possible sur des voies précises",
"it": "Larrampicata 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 descalade 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 descalader à la moulinette ?",
"it": "È possibile arrampicarsi con la corda dallalto 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": "Lescalade à 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": "Lescalade à la moulinette nest 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": "Larrampicata sportiva è possibile qua",
"hu": "Itt lehetőség van sportmászásra",
"fr": "De lescalade 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": "Larrampicata sportiva non è possibile qua",
"hu": "Itt nincs lehetőség sportmászásra",
"fr": "Lescalade 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? <br/><span class='subtle'>(Dit is klimmen met klemblokjes en friends)</span>",
"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": "Larrampicata 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": "Larrampicata 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.<div class='subtle'>This is without relays and indicates how much quickdraws a climber needs</div>"
},
"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 na 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 descalade, rocher avec une ou peu de voie permettant descalader 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 descalade, rocher avec plusieurs voies descalades",
"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é descalade",
"it": "una opportunità di arrampicata"
},
"description": {
"nl": "Een klimgelegenheid",
"de": "Eine Klettergelegenheit",
"en": "A climbing opportunity",
"ja": "登坂教室",
"nb_NO": "En klatremulighet",
"fr": "Opportunité descalade",
"it": "Unopportunità 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 => `<li><a href='#${p.id}'>${p.name ?? 'climbing route'}</a> (<b>${p['climbing:grade:french'] ?? 'unknown difficulty'}</b>, ${p['climbing:length'] ?? 'unkown length'} meter)</li>`).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
}

View file

@ -0,0 +1,308 @@
{
"id": "climbing_area",
"name": {
"nl": "Klimgelegenheden",
"de": "Klettermöglichkeiten",
"en": "Climbing opportunities",
"ja": "登坂教室",
"fr": "Opportunité descalade",
"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é descalade",
"it": "Opportunità di arrampicata"
},
"mappings": [
{
"if": "climbing=crag",
"then": {
"en": "Climbing crag <b>{name}</b>",
"fr": "Mur descalade <b>{name}</b>",
"it": "Muro da arrampicata <b>{name}</b>",
"de": "Klettergarten <b>{name}</b>"
}
},
{
"if": {
"and": [
{
"or": [
"climbing=area",
"climbing=site"
]
},
"name~*"
]
},
"then": {
"en": "Climbing area <b>{name}</b>",
"nl": "Klimsite <b>{name}</b>",
"fr": "Zone descalade <b>{name}</b>",
"de": "Klettergebiet <b>{name}</b>",
"it": "Area di arrampicata <b>{name}</b>"
}
},
{
"if": {
"or": [
"climbing=site",
"climbing=area"
]
},
"then": {
"en": "Climbing site",
"nl": "Klimsite",
"fr": "Site descalade",
"de": "Klettergebiet",
"it": "Sito di arrampicata",
"ca": "Llocs d'escalada"
}
},
{
"if": "name~*",
"then": {
"nl": "Klimgelegenheid <b>{name}</b>",
"en": "Climbing opportunity <b>{name}</b>",
"fr": "Opportunité descalade <b>{name}</b>",
"de": "Klettermöglichkeit <b>{name}</b>",
"it": "Opportunità di arrampicata <b>{name}</b>"
}
}
]
},
"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 => `<li><a href='#${p.id}'>${p.name ?? 'climbing route'}</a> (<b class='climbing-${p['__difficulty:char']} rounded-full p-l-1 p-r-1'>${p['climbing:grade:french'] ?? 'unknown difficulty'}</b>, ${p['climbing:length'] ?? 'unkown length'} meter)</li>`).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": "<h3>Length overview</h3>{histogram(_length_hist)}",
"fr": "<h3>Résumé de longueur</h3>{histogram(_length_hist)}",
"de": "<h3>Längenübersicht</h3>{histogram(_length_hist)}",
"it": "<h3>Riassunto della lunghezza</h3>{histogram(_length_hist)}"
},
"condition": "_length_hist!~\\[\\]",
"id": "Contained routes length hist"
},
{
"render": {
"en": "<h3>Grades overview</h3>{histogram(_difficulty_hist)}",
"fr": "<h3>Résumé des difficultés</h3>{histogram(_difficulty_hist)}",
"de": "<h3>Schwierigkeitsübersicht</h3>{histogram(_difficulty_hist)}",
"it": "<h3>Riassunto delle difficoltà</h3>{histogram(_difficulty_hist)}"
},
"condition": "_difficulty_hist!~\\[\\]",
"id": "Contained routes hist"
},
{
"render": {
"en": "<h3>Contains {_contained_climbing_routes_count} routes</h3> <ul>{_contained_climbing_routes}</ul>",
"fr": "<h3>Contient {_contained_climbing_routes_count} voies</h3> <ul>{_contained_climbing_routes}</ul>",
"it": "<h3>Contiene {_contained_climbing_routes_count} vie</h3> <ul>{_contained_climbing_routes}</ul>",
"de": "<h3> Enthält {_contained_climbing_routes_count} Routen</h3> <ul>{_contained_climbing_routes}</ul>"
},
"condition": "_contained_climbing_routes~*",
"id": "Contained_climbing_routes"
},
{
"render": {
"en": "<strong>{name}</strong>",
"nl": "<strong>{name}</strong>",
"de": "<strong>{name}</strong>",
"ca": "<strong>{name}</strong>",
"fr": "<strong>{name}</strong>",
"id": "<strong>{name}</strong>",
"ru": "<strong>{name}</strong>",
"ja": "<strong>{name}</strong>",
"it": "<strong>{name}</strong>"
},
"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 na 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 descalade, rocher avec une ou peu de voie permettant descalader 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 descalade, rocher avec plusieurs voies descalades",
"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é descalade",
"it": "una opportunità di arrampicata"
},
"description": {
"nl": "Een klimgelegenheid",
"de": "Eine Klettergelegenheit",
"en": "A climbing opportunity",
"ja": "登坂教室",
"nb_NO": "En klatremulighet",
"fr": "Opportunité descalade",
"it": "Unopportunità 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"
}
}
]
}

View file

@ -55,15 +55,7 @@
"images",
{
"render": {
"en": "<strong>{name}</strong>",
"nl": "<strong>{name}</strong>",
"de": "<strong>{name}</strong>",
"ca": "<strong>{name}</strong>",
"fr": "<strong>{name}</strong>",
"id": "<strong>{name}</strong>",
"ru": "<strong>{name}</strong>",
"ja": "<strong>{name}</strong>",
"it": "<strong>{name}</strong>"
"*": "<strong>{name}</strong>"
},
"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 larrampicata 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 larrampicata 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 larrampicata 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 larrampicata di velocità"
},
"hideInAnswer": true
}
]
}
],
"mapRendering": [
{

View file

@ -9,6 +9,9 @@
"fr": "Opportunités descalade ?",
"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é descalade ?",
"it": "Unopportunità di arrampicata?"
},
"tagRenderings": [
{
"id": "climbing-opportunity-name",

View file

@ -9,6 +9,9 @@
"fr": "Voies descalade",
"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 datteindre 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 <div class='subtle'>This is without relays and indicates how much quickdraws a climber needs</div>",
"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 na 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": "<h3>Description</h3><br/>{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 <a href='#{_embedding_features_with_rock:id}'>on the surrounding crag</a>",
"fr": "Le type de roche est {_embedding_features_with_rock:rock} selon <a href='#{_embedding_features_with_rock:id}'>le mur</a>",
@ -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": "<div class='w-max p-1 rounded-xl' style='background: white;'>{name} <span class='climbing-{__difficulty:char}'>{climbing:grade:french}</span></div>"
},
{
"if": "name~*",
"then": "<div style='background: white; padding: 0.25em; border-radius:0.5em'>{name}</div>"
"then": "<div class='w-max p-1 rounded-xl' style='background: white;'>{name}</div>"
}
]
}

View file

@ -52,6 +52,10 @@
"helperArgs": [
"name",
{
"notInstanceOf": [
"Q79007",
"Q22698"
],
"removePostfixes": [
"steenweg",
"heirbaan",
@ -70,7 +74,9 @@
"wegel",
"kerk",
"church",
"kaai"
"kaai",
"park",
"parque"
]
}
]

View file

@ -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}",

View file

@ -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": {

View file

@ -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;
}

View file

@ -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": "<div class='flex' style='word-wrap: normal; padding-right: 0.25rem;'><img src='./assets/themes/climbing/height.svg' style='height: 1.75rem;'/>{climbing:length}m</div>",
"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": "<div class='flex' style='padding-right: 0.25rem;'><img src='./assets/themes/climbing/carabiner.svg' style='width: 1rem;'/>{climbing:bolts}</div>"
"render": "<div class='flex' style='word-wrap: normal; padding-right: 0.25rem;'><img src='./assets/themes/climbing/height.svg' style='height: 1.75rem;'/>{climbing:length}m</div>",
"condition": "climbing:length~*"
},
{
"if": "climbing:bolted=yes",
"then": "<img src='./assets/themes/climbing/carabiner.svg' style='width:2rem; height:2rem'/>"
"mappings": [
{
"if": "__bolts_max~*",
"then": "<div class='flex' style='padding-right: 0.25rem;'><img src='./assets/themes/climbing/carabiner.svg' style='width: 1rem;'/>{__bolts_max}</div>"
},
{
"if": "climbing:bolted=yes",
"then": "<img src='./assets/themes/climbing/carabiner.svg' style='width:2rem; height:2rem'/>"
}
]
},
{
"id": "Min difficulty",
"condition": "__difficulty_min~*",
"render": "<div class='w-8 flex justify-center rounded-left-full climbing-{__difficulty_min:char}' style='margin-right: -5px;'> {__difficulty_min}</div>"
},
{
"id": "max difficulty",
"condition": "__difficulty_max~*",
"render": "<div class='w-8 flex justify-center rounded-right-full climbing-{__difficulty_max:char}'> {__difficulty_max}</div>"
},
{
"render": "<div class='flex justify-center rounded-full pl-1 pr-1 climbing-{__difficulty:char}'> {climbing:grade:french}</div>"
}
]
},
{
"mappings": [
{
"if": "climbing:grade:french~3.*",
"then": "<div class='rounded-full px-1' style='background-color:#ebf224'> {climbing:grade:french}</div>"
},
{
"if": "climbing:grade:french~4.*",
"then": "<div class='rounded-full pl-2 pr-2' style='background-color:#7af224'> {climbing:grade:french}</div>"
},
{
"if": "climbing:grade:french~5.*",
"then": "<div class='rounded-full pl-2 pr-2' style='background-color:#f24a24'> {climbing:grade:french}</div>"
},
{
"if": "climbing:grade:french~6.*",
"then": "<div class='text-white rounded-full pl-2 pr-2' style='background-color:#244af2'> {climbing:grade:french}</div>"
},
{
"if": "climbing:grade:french~7.*",
"then": "<div class='text-white rounded-full pl-2 pr-2' style='background-color:#e904ed'> {climbing:grade:french}</div>"
},
{
"if": "climbing:grade:french~*",
"then": "<div class='text-white rounded-full px-2' style='background-colour:black'> {climbing:grade:french}</div>"
}
]
}
],
"+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": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> publicly accessible<br/>{_embedding_feature:access:description}",
"nl": "<span class='subtle'>Een <a href='#{_embedding_feature:id}'>omvattend element</a> geeft aan dat dit <span>publiek toegangkelijk is</span><br/>{_embedding_feature:access:description}",
"fr": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique un </span> accès libre<br/>{_embedding_feature:access:description}",
"it": "<span class='subtle'>L <a href='#{_embedding_feature:id}'>elemento in cui è contenuto</a> indica che è</span> pubblicamente accessibile<br/>{_embedding_feature:access:description}",
"de": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> gibt an, dass es </span>öffentlich zugänglich ist<br/>{_embedding_feature:access:description}"
}
},
{
"if": "_embedding_feature:access=permit",
"then": {
"en": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that </span> a permit is needed to access<br/>{_embedding_feature:access:description}",
"nl": "<span class='subtle'>Een <a href='#{_embedding_feature:id}'>omvattend element</a> geeft aan dat</span> een toelating nodig is om hier te klimmen<br/>{_embedding_feature:access:description}",
"fr": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique qu</span> une autorisation daccès est nécessaire<br/>{_embedding_feature:access:description}",
"it": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>elemento che lo contiene</a> indica che </span> è richiesto unautorizzazione per accedervi<br/>{_embedding_feature:access:description}",
"de": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> besagt, dass </span> eine Genehmigung erforderlich ist für den Zugang zu<br/>{_embedding_feature:access:description}"
}
},
{
"if": "_embedding_feature:access=customers",
"then": {
"en": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> only accessible to customers<br/>{_embedding_feature:access:description}",
"fr": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique que </span> laccès est réservés aux clients<br/>{_embedding_feature:access:description}",
"it": "<span class='subtle'>L <a href='#{_embedding_feature:id}'>elemento che lo contiene</a> indica che è</span> accessibile solo ai clienti<br/>{_embedding_feature:access:description}",
"de": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> besagt, dass es nur für Kunden</span> zugänglich ist<br/>{_embedding_feature:access:description}"
}
},
{
"if": "_embedding_feature:access=members",
"then": {
"en": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> only accessible to club members<br/>{_embedding_feature:access:description}",
"fr": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique que </span> laccès est réservé aux membres<br/>{_embedding_feature:access:description}",
"it": "<span class='subtle'>L <a href='#{_embedding_feature:id}'>elemento che lo contiene</a> indica che è </span> accessibile solamente ai membri del club<br/>{_embedding_feature:access:description}",
"de": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> besagt, dass es </span>nur für Mitglieder zugänglich ist<br/>{_embedding_feature:access:description}"
}
},
{
"if": "_embedding_feature:access=no",
"then": "Not accessible as stated by <a href='#{_embedding_feature:id}'>the containing feature</a>"
}
],
"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-til un site avec plus dinformations (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": "<a href='{url}' target='_blank'>{url}</a>",
"freeform": {
"key": "url",
"type": "url"
}
},
{
"id": "Access from containing feature",
"mappings": [
{
"if": "_embedding_feature:access=yes",
"then": {
"en": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> publicly accessible<br/>{_embedding_feature:access:description}",
"nl": "<span class='subtle'>Een <a href='#{_embedding_feature:id}'>omvattend element</a> geeft aan dat dit <span>publiek toegangkelijk is</span><br/>{_embedding_feature:access:description}",
"fr": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique un </span> accès libre<br/>{_embedding_feature:access:description}",
"it": "<span class='subtle'>L <a href='#{_embedding_feature:id}'>elemento in cui è contenuto</a> indica che è</span> pubblicamente accessibile<br/>{_embedding_feature:access:description}",
"de": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> gibt an, dass es </span>öffentlich zugänglich ist<br/>{_embedding_feature:access:description}"
}
"condition": "_embedding_feature:access~*"
},
{
"if": "_embedding_feature:access=permit",
"then": {
"en": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that </span> a permit is needed to access<br/>{_embedding_feature:access:description}",
"nl": "<span class='subtle'>Een <a href='#{_embedding_feature:id}'>omvattend element</a> geeft aan dat</span> een toelating nodig is om hier te klimmen<br/>{_embedding_feature:access:description}",
"fr": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique qu</span> une autorisation daccès est nécessaire<br/>{_embedding_feature:access:description}",
"it": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>elemento che lo contiene</a> indica che </span> è richiesto unautorizzazione per accedervi<br/>{_embedding_feature:access:description}",
"de": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> besagt, dass </span> eine Genehmigung erforderlich ist für den Zugang zu<br/>{_embedding_feature:access:description}"
}
},
{
"if": "_embedding_feature:access=customers",
"then": {
"en": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> only accessible to customers<br/>{_embedding_feature:access:description}",
"fr": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique que </span> laccès est réservés aux clients<br/>{_embedding_feature:access:description}",
"it": "<span class='subtle'>L <a href='#{_embedding_feature:id}'>elemento che lo contiene</a> indica che è</span> accessibile solo ai clienti<br/>{_embedding_feature:access:description}",
"de": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> besagt, dass es nur für Kunden</span> zugänglich ist<br/>{_embedding_feature:access:description}"
}
},
{
"if": "_embedding_feature:access=members",
"then": {
"en": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> only accessible to club members<br/>{_embedding_feature:access:description}",
"fr": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique que </span> laccès est réservé aux membres<br/>{_embedding_feature:access:description}",
"it": "<span class='subtle'>L <a href='#{_embedding_feature:id}'>elemento che lo contiene</a> indica che è </span> accessibile solamente ai membri del club<br/>{_embedding_feature:access:description}",
"de": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> besagt, dass es </span>nur für Mitglieder zugänglich ist<br/>{_embedding_feature:access:description}"
}
},
{
"if": "_embedding_feature:access=no",
"then": "Not accessible as stated by <a href='#{_embedding_feature:id}'>the containing feature</a>"
}
],
"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 daccè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 unautorizzazione 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 daccè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 unautorizzazione 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 <b>{canonical(climbing:length)}</b> lang",
"en": "The routes are <b>{canonical(climbing:length)}</b> long on average",
"nl": "De klimroutes zijn gemiddeld <b>{canonical(climbing:length)}</b> lang",
"ja": "ルートの長さは平均で<b>{canonical(climbing:length)}</b>です",
"fr": "Les voies font <b>{canonical(climbing:length)}</b> de long en moyenne",
"it": "Le vie sono lunghe mediamente <b>{canonical(climbing:length)}</b>",
"es": "Las rotas tienen una longitud media de <b>{canonical(climbing:length)}</b>"
},
"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": "Lescalade 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": "Lescalade de bloc est possible",
"it": "Larrampicata 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": "Lescalade de bloc nest pas possible",
"it": "Larrampicata 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": "Lescalade de bloc est possible sur des voies précises",
"it": "Larrampicata 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 descalade 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 descalader à la moulinette ?",
"it": "È possibile arrampicarsi con la corda dallalto 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": "Lescalade à 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": "Lescalade à la moulinette nest 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": "Larrampicata sportiva è possibile qua",
"hu": "Itt lehetőség van sportmászásra",
"fr": "De lescalade 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": "Larrampicata sportiva non è possibile qua",
"hu": "Itt nincs lehetőség sportmászásra",
"fr": "Lescalade 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? <br/><span class='subtle'>(Dit is klimmen met klemblokjes en friends)</span>",
"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": "Larrampicata 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": "Larrampicata 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 larrampicata 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 larrampicata 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 larrampicata 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 larrampicata di velocità"
},
"hideInAnswer": true
}
"questions",
"reviews"
]
},
"questions",
"reviews"
]
}
}
}
]
}

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CSS-debugging for climbing theme</title>
<link href="/css/index-tailwind-output.css" rel="stylesheet"/>
<link href="/assets/themes/climbing/climbing.css" rel="stylesheet"/>
</head>
<body>
<div class="rounded-full climbing-2">2</div>
<div class="rounded-full climbing-3">3</div>
<div class="rounded-full climbing-4">4</div>
<div class="rounded-full climbing-5">5</div>
<div class="rounded-full climbing-6">6</div>
<div class="rounded-full climbing-7">7</div>
<div class="rounded-full climbing-8">8</div>
<div class="rounded-full climbing-9">9</div>
<div class="rounded-full climbing-">X</div>
</body>
</html>

View file

@ -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']",

View file

@ -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;
}
}

View file

@ -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 {

View file

@ -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": "すべての樹木をマッピングする",

View file

@ -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;
}

View file

@ -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;
}

View file

@ -420,7 +420,7 @@
}
}
},
"climbing": {
"climbing_area": {
"tagRenderings": {
"name": {
"render": "<strong>{name}</strong>"
@ -441,13 +441,6 @@
}
}
},
"climbing_gym": {
"tagRenderings": {
"name": {
"render": "<strong>{name}</strong>"
}
}
},
"climbing_opportunity": {
"tagRenderings": {
"climbing-opportunity-name": {

View file

@ -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 <b>{canonical(climbing:length)}</b> 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": "<strong>{name}</strong>"
"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": "<strong>{name}</strong>"
},
"Rock type": {
"Rock type via embedded feature": {
"render": "Der Gesteinstyp ist {_embedding_features_with_rock:rock}, wie <a href='#{_embedding_features_with_rock:id}'>auf dem umgebenden Felsen angegeben</a>"
},
"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": {

View file

@ -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 <b>{canonical(climbing:length)}</b> 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.<div class='subtle'>This is without relays and indicates how much quickdraws a climber needs</div>"
},
"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": "<strong>{name}</strong>"
"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": "<strong>{name}</strong>"
},
"Rock type": {
"Rock type via embedded feature": {
"render": "The rock type is {_embedding_features_with_rock:rock} as stated <a href='#{_embedding_features_with_rock:id}'>on the surrounding crag</a>"
},
"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 <div class='subtle'>This is without relays and indicates how much quickdraws a climber needs</div>"
}
},
"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": {

View file

@ -1036,7 +1036,66 @@
}
},
"climbing": {
"description": "Opportunité descalade",
"tagRenderings": {
"average_length": {
"question": "Quelle est la longueur moyenne des voies en mètres ?",
"render": "Les voies font <b>{canonical(climbing:length)}</b> de long en moyenne"
},
"bouldering": {
"mappings": {
"0": {
"then": "Lescalade de bloc est possible"
},
"1": {
"then": "Lescalade de bloc nest pas possible"
},
"2": {
"then": "Lescalade de bloc est possible sur des voies précises"
},
"3": {
"then": "Il y a {climbing:boulder} voies descalade de bloc"
}
},
"question": "Lescalade 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 lescalade est possible ici"
},
"1": {
"then": "Lescalade est impossible ici"
}
}
},
"toprope": {
"mappings": {
"0": {
"then": "Lescalade à la moulinette est possible"
},
"1": {
"then": "Lescalade à la moulinette nest pas possible"
},
"2": {
"then": "{climbing:toprope} voies sont équipées de moulinettes"
}
},
"question": "Est-il possible descalader à la moulinette ?"
},
"website": {
"question": "Existe-til un site avec plus dinformations (ex : topographie) ?"
}
}
},
"climbing_area": {
"name": "Opportunité descalade",
"presets": {
"0": {
@ -1134,8 +1193,7 @@
"name": "Salle descalade",
"tagRenderings": {
"name": {
"question": "Quel est le nom de la salle descalade ?",
"render": "<strong>{name}</strong>"
"question": "Quel est le nom de la salle descalade ?"
}
},
"title": {
@ -1148,7 +1206,6 @@
}
},
"climbing_opportunity": {
"description": "Opportunité descalade ?",
"name": "Opportunités descalade ?",
"tagRenderings": {
"climbing-opportunity-name": {
@ -1181,18 +1238,6 @@
}
},
"tagRenderings": {
"Bolts": {
"mappings": {
"0": {
"then": "Cette voie na pas de prises"
},
"1": {
"then": "Cette voie na pas de prises"
}
},
"question": "Combien de prises cette voie possède avant datteindre 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 descalade ?",
"render": "<strong>{name}</strong>"
},
"Rock type": {
"Rock type via embedded feature": {
"render": "Le type de roche est {_embedding_features_with_rock:rock} selon <a href='#{_embedding_features_with_rock:id}'>le mur</a>"
},
"bolts": {
"mappings": {
"0": {
"then": "Cette voie na pas de prises"
}
},
"question": "Combien de prises cette voie possède avant datteindre la moulinette ?",
"render": "Cette voie a {climbing:bolts} prises"
}
},
"title": {

View file

@ -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",

View file

@ -151,7 +151,7 @@
}
}
},
"climbing": {
"climbing_area": {
"tagRenderings": {
"name": {
"render": "<strong>{name}</strong>"
@ -165,13 +165,6 @@
}
}
},
"climbing_gym": {
"tagRenderings": {
"name": {
"render": "<strong>{name}</strong>"
}
}
},
"climbing_opportunity": {
"tagRenderings": {
"climbing-opportunity-name": {

View file

@ -792,7 +792,84 @@
}
},
"climbing": {
"description": "Unopportunità di arrampicata",
"tagRenderings": {
"average_length": {
"question": "Quale è la lunghezza (media) delle vie in metri?",
"render": "Le vie sono lunghe mediamente <b>{canonical(climbing:length)}</b>"
},
"bouldering": {
"mappings": {
"0": {
"then": "Larrampicata su massi è possibile qua"
},
"1": {
"then": "Larrampicata su massi non è possibile qua"
},
"2": {
"then": "Larrampicata 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": "Larrampicata sportiva è possibile qua"
},
"1": {
"then": "Larrampicata 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 dallalto qua?"
},
"trad_climbing": {
"mappings": {
"0": {
"then": "Larrampicata tradizionale è possibile qua"
},
"1": {
"then": "Larrampicata 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 larrampicata di velocità"
},
"1": {
"then": "Non è presente una parete per larrampicata di velocità"
},
"2": {
"then": "Sono presenti {climbing:speed} pareti per larrampicata di velocità"
}
},
"question": "È presente una prete per larrampicata di velocità?"
},
"name": {
"question": "Qual è il nome di questa palestra di arrampicata?",
"render": "<strong>{name}</strong>"
"question": "Qual è il nome di questa palestra di arrampicata?"
}
},
"title": {
@ -904,7 +994,6 @@
}
},
"climbing_opportunity": {
"description": "Unopportunità 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": "<strong>{name}</strong>"
},
"Rock type": {
"Rock type via embedded feature": {
"render": "Il tipo di roccia è {_embedding_features_with_rock:rock} come dichiarato sul <a href='#{_embedding_features_with_rock:id}'>muro circostante</a>"
},
"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": {

View file

@ -122,7 +122,84 @@
}
},
"climbing": {
"description": "登坂教室",
"tagRenderings": {
"average_length": {
"question": "ルートの(平均)長さはメートル単位でいくつですか?",
"render": "ルートの長さは平均で<b>{canonical(climbing:length)}</b>です"
},
"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": "<strong>{name}</strong>"
"question": "このクライミングジムは何という名前ですか?"
}
},
"title": {
@ -192,7 +282,6 @@
}
},
"climbing_opportunity": {
"description": "登坂教室?",
"name": "登坂教室?",
"tagRenderings": {
"climbing-opportunity-name": {

View file

@ -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": {

View file

@ -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 <b>{canonical(climbing:length)}</b> 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? <br/><span class='subtle'>(Dit is klimmen met klemblokjes en friends)</span>"
},
"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": "<strong>{name}</strong>"
"question": "Wat is de naam van dit Klimzaal?"
}
},
"title": {
@ -2400,7 +2490,6 @@
}
},
"climbing_opportunity": {
"description": "Een klimgelegenheid?",
"name": "Klimgelegenheiden?",
"tagRenderings": {
"climbing-opportunity-name": {

View file

@ -764,6 +764,23 @@
}
},
"climbing": {
"tagRenderings": {
"sportclimbing": {
"mappings": {
"0": {
"then": "Здесь можно заняться спортивным скалолазанием"
},
"1": {
"then": "Спортивное скалолазание здесь невозможно"
}
}
},
"website": {
"question": "Есть ли (неофициальный) веб-сайт с более подробной информацией (напр., topos)?"
}
}
},
"climbing_area": {
"tagRenderings": {
"name": {
"render": "<strong>{name}</strong>"
@ -790,11 +807,6 @@
"climbing_gym": {
"description": "Комплекс скалолазания",
"name": "Комплексы скалолазания",
"tagRenderings": {
"name": {
"render": "<strong>{name}</strong>"
}
},
"title": {
"render": "Комплекс скалолазания"
}

View file

@ -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"
}
}
}
}
}

View file

@ -269,138 +269,54 @@
"climbing": {
"description": "Eine Karte mit Klettermöglichkeiten wie Kletterhallen, Kletterparks oder Felsen.",
"descriptionTail": "<p><strong>kletterspots.de</strong> wird betrieben von <a href='https://utopicode.de/?ref=kletterspots' target='_blank'>Christian Neumann</a>. Bitte <a href='https://utopicode.de/kontakt/?project=kletterspots&ref=kletterspots' target='blank'>melden Sie sich</a>, wenn Sie Feedback oder Fragen haben.</p><p>Das Projekt nutzt <a href='https://www.openstreetmap.org/' target='_blank'>OpenStreetMap</a> Daten und basiert auf der freien Software <a href='https://github.com/pietervdvn/MapComplete' target='_blank'>MapComplete</a>.</p>",
"overrideAll": {
"tagRenderings+": {
"0": {
"question": "Gibt es eine (inoffizielle) Website mit mehr Informationen (z.B. Topos)?"
},
"1": {
"mappings": {
"layers": {
"0": {
"override": {
"tagRenderings+": {
"0": {
"then": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> gibt an, dass es </span>öffentlich zugänglich ist<br/>{_embedding_feature:access:description}"
"mappings": {
"0": {
"then": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> gibt an, dass es </span>öffentlich zugänglich ist<br/>{_embedding_feature:access:description}"
},
"1": {
"then": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> besagt, dass </span> eine Genehmigung erforderlich ist für den Zugang zu<br/>{_embedding_feature:access:description}"
},
"2": {
"then": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> besagt, dass es nur für Kunden</span> zugänglich ist<br/>{_embedding_feature:access:description}"
},
"3": {
"then": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> besagt, dass es </span>nur für Mitglieder zugänglich ist<br/>{_embedding_feature:access:description}"
}
}
},
"1": {
"then": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> besagt, dass </span> eine Genehmigung erforderlich ist für den Zugang zu<br/>{_embedding_feature:access:description}"
},
"2": {
"then": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> besagt, dass es nur für Kunden</span> zugänglich ist<br/>{_embedding_feature:access:description}"
},
"3": {
"then": "<span class='subtle'>Das <a href='#{_embedding_feature:id}'>enthaltende Objekt</a> besagt, dass es </span>nur für Mitglieder zugänglich ist<br/>{_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 <b>{canonical(climbing:length)}</b> 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": "<b>Nicht</b> erstellt von {search}"
}
}
}
},
"name": "Schwerpunkte von Änderungssätzen",
"tagRenderings": {
"contributor": {
"render": "Änderung wurde von <a href='https://openstreetmap.org/user/{_last_edit:contributor}' target='_blank'>{_last_edit:contributor}</a> gemacht"
},
"render_id": {
"render": "Änderung <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>"
},
"theme": {
"mappings": {
"0": {
"then": "Änderung mit <b>inoffiziellem</b> Thema <a href='https://mapcomplete.osm.be/theme.html?userlayout={theme}'>{theme}</a>"
}
},
"render": "Änderung mit Thema <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>"
}
},
"title": {
"render": "Änderungen für {theme}"
}
},
"1": {
"override": {
"tagRenderings": {
"link_to_more": {
"render": "Weitere Statistiken finden Sie <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>hier</a>"
}
}
}
}
},
"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, ...) <br/><br/>Wenn eine Karte fehlt, können Sie diese leicht auf OpenStreetMap kartieren.",
"shortDescription": "Dieses Thema zeigt alle (touristischen) Karten, die OpenStreetMap kennt",

View file

@ -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 <a href='https://utopicode.de/en/?ref=kletterspots' target='_blank'>Christian Neumann</a>. Please <a href='https://utopicode.de/en/contact/?project=kletterspots&ref=kletterspots' target='blank'>get in touch</a> if you have feedback or questions.</p><p>The project uses data of the <a href='https://www.openstreetmap.org/' target='_blank'>OpenStreetMap</a> project.</p>",
"overrideAll": {
"tagRenderings+": {
"0": {
"question": "Is there a (unofficial) website with more informations (e.g. topos)?"
},
"1": {
"mappings": {
"layers": {
"0": {
"override": {
"tagRenderings+": {
"0": {
"then": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> publicly accessible<br/>{_embedding_feature:access:description}"
"mappings": {
"0": {
"then": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> publicly accessible<br/>{_embedding_feature:access:description}"
},
"1": {
"then": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that </span> a permit is needed to access<br/>{_embedding_feature:access:description}"
},
"2": {
"then": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> only accessible to customers<br/>{_embedding_feature:access:description}"
},
"3": {
"then": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> only accessible to club members<br/>{_embedding_feature:access:description}"
}
}
},
"1": {
"then": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that </span> a permit is needed to access<br/>{_embedding_feature:access:description}"
},
"2": {
"then": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> only accessible to customers<br/>{_embedding_feature:access:description}"
},
"3": {
"then": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> only accessible to club members<br/>{_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 <b>{canonical(climbing:length)}</b> 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"
}
}
}
}
}

View file

@ -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"
}
}
}
}
}

View file

@ -269,106 +269,54 @@
"climbing": {
"description": "Cette carte indique les sites descalades comme les salles descalade ou les sites naturels.",
"descriptionTail": "La carte des sites d'escalade a été créée par <a href='https://utopicode.de/en/?ref=kletterspots' target='_blank'>Christian Neumann</a>. Merci de le <a href='https://utopicode.de/en/contact/?project=kletterspots&ref=kletterspots' target='blank'>contacter</a> pour des avis ou des questions.</p><p>Ce projet utilise les données <a href='https://www.openstreetmap.org/' target='_blank'>OpenStreetMap</a>.</p>",
"overrideAll": {
"tagRenderings+": {
"0": {
"question": "Existe-til un site avec plus dinformations (ex : topographie) ?"
},
"1": {
"mappings": {
"layers": {
"0": {
"override": {
"tagRenderings+": {
"0": {
"then": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique un </span> accès libre<br/>{_embedding_feature:access:description}"
"mappings": {
"0": {
"then": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique un </span> accès libre<br/>{_embedding_feature:access:description}"
},
"1": {
"then": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique qu</span> une autorisation daccès est nécessaire<br/>{_embedding_feature:access:description}"
},
"2": {
"then": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique que </span> laccès est réservés aux clients<br/>{_embedding_feature:access:description}"
},
"3": {
"then": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique que </span> laccès est réservé aux membres<br/>{_embedding_feature:access:description}"
}
}
},
"1": {
"then": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique qu</span> une autorisation daccès est nécessaire<br/>{_embedding_feature:access:description}"
},
"2": {
"then": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique que </span> laccès est réservés aux clients<br/>{_embedding_feature:access:description}"
},
"3": {
"then": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>élément englobant</a> indique que </span> laccès est réservé aux membres<br/>{_embedding_feature:access:description}"
}
}
},
"2": {
"mappings": {
"0": {
"then": "Libre daccès"
},
"1": {
"then": "Une autorisation est nécessaire"
},
"2": {
"then": "Réservé aux clients"
},
"3": {
"then": "Réservé aux membres"
"mappings": {
"0": {
"then": "Libre daccè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 <b>{canonical(climbing:length)}</b> 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": "Lescalade de bloc est possible"
},
"1": {
"then": "Lescalade de bloc nest pas possible"
},
"2": {
"then": "Lescalade de bloc est possible sur des voies précises"
},
"3": {
"then": "Il y a {climbing:boulder} voies descalade de bloc"
}
},
"question": "Lescalade de bloc est-elle possible ici ?"
},
"8": {
"mappings": {
"0": {
"then": "Lescalade à la moulinette est possible"
},
"1": {
"then": "Lescalade à la moulinette nest pas possible"
},
"2": {
"then": "{climbing:toprope} voies sont équipées de moulinettes"
}
},
"question": "Est-il possible descalader à la moulinette ?"
},
"9": {
"mappings": {
"0": {
"then": "De lescalade est possible ici"
},
"1": {
"then": "Lescalade est impossible ici"
}
}
}
},
"units+": {
"0": {
"applicableUnits": {
"0": {
"human": " mètres"
},
"1": {
"human": " pieds"
"applicableUnits": {
"0": {
"human": " mètres"
},
"1": {
"human": " pieds"
}
}
}
}
}

View file

@ -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 <a href='https://utopicode.de/en/?ref=kletterspots' target='_blank'>Christian Neumann</a> készítette. Ha észrevétele vagy kérdése van, kérjük, <a href='https://utopicode.de/en/contact/?project=kletterspots&ref=kletterspots' target='blank'>vele lépjen kapcsolatba</a>.</p> <p>A projekt az <a href='https://www.openstreetmap.org/' target='_blank'>OpenStreetMap</a> adatait használja</p>",
"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": {

View file

@ -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 <a href='https://utopicode.de/en/?ref=kletterspots' target='_blank'>Christian Neumann</a>. Si prega di <a href='https://utopicode.de/en/contact/?project=kletterspots&ref=kletterspots' target='blank'>scrivere qua</a> se si hanno commenti o domande da fare.</p><p>Il progetto usa i dati del progetto <a href='https://www.openstreetmap.org/' target='_blank'>OpenStreetMap</a>.</p>",
"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": "<span class='subtle'>L <a href='#{_embedding_feature:id}'>elemento in cui è contenuto</a> indica che è</span> pubblicamente accessibile<br/>{_embedding_feature:access:description}"
"mappings": {
"0": {
"then": "<span class='subtle'>L <a href='#{_embedding_feature:id}'>elemento in cui è contenuto</a> indica che è</span> pubblicamente accessibile<br/>{_embedding_feature:access:description}"
},
"1": {
"then": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>elemento che lo contiene</a> indica che </span> è richiesto unautorizzazione per accedervi<br/>{_embedding_feature:access:description}"
},
"2": {
"then": "<span class='subtle'>L <a href='#{_embedding_feature:id}'>elemento che lo contiene</a> indica che è</span> accessibile solo ai clienti<br/>{_embedding_feature:access:description}"
},
"3": {
"then": "<span class='subtle'>L <a href='#{_embedding_feature:id}'>elemento che lo contiene</a> indica che è </span> accessibile solamente ai membri del club<br/>{_embedding_feature:access:description}"
}
}
},
"1": {
"then": "<span class='subtle'>L<a href='#{_embedding_feature:id}'>elemento che lo contiene</a> indica che </span> è richiesto unautorizzazione per accedervi<br/>{_embedding_feature:access:description}"
},
"2": {
"then": "<span class='subtle'>L <a href='#{_embedding_feature:id}'>elemento che lo contiene</a> indica che è</span> accessibile solo ai clienti<br/>{_embedding_feature:access:description}"
},
"3": {
"then": "<span class='subtle'>L <a href='#{_embedding_feature:id}'>elemento che lo contiene</a> indica che è </span> accessibile solamente ai membri del club<br/>{_embedding_feature:access:description}"
}
}
},
"2": {
"mappings": {
"0": {
"then": "Pubblicamente accessibile a chiunque"
},
"1": {
"then": "È necessario avere unautorizzazione 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 unautorizzazione 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 <b>{canonical(climbing:length)}</b>"
},
"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": "Larrampicata su massi è possibile qua"
},
"1": {
"then": "Larrampicata su massi non è possibile qua"
},
"2": {
"then": "Larrampicata 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 dallalto qua?"
},
"9": {
"mappings": {
"0": {
"then": "Larrampicata sportiva è possibile qua"
},
"1": {
"then": "Larrampicata 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": "Larrampicata tradizionale è possibile qua"
},
"1": {
"then": "Larrampicata 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 larrampicata di velocità"
},
"1": {
"then": "Non è presente una parete per larrampicata di velocità"
},
"2": {
"then": "Sono presenti {climbing:speed} pareti per larrampicata di velocità"
}
},
"question": "È presente una prete per larrampicata di velocità?"
}
},
"units+": {
"0": {
"applicableUnits": {
"0": {
"human": " metri"
},
"1": {
"human": " piedi"
"applicableUnits": {
"0": {
"human": " metri"
},
"1": {
"human": " piedi"
}
}
}
}
}

View file

@ -255,98 +255,6 @@
"climbing": {
"description": "この地図には、自然の中のクライミングジム、ボルダリングホール、岩など、さまざまなクライミングの機会があります。",
"descriptionTail": "登山地図はもともと <a href='https://utopicode.de/en/?ref=kletterspots' target='_blank'>Christian Neumann</a> によって作成されたものです。フィードバックや質問がありましたら、<a href='https://utopicode.de/en/contact/?project=kletterspots&ref=kletterspots' target='blank'>ご連絡</a>ください。</p><p>このプロジェクトでは、<a href='https://www.openstreetmap.org/' target='_blank'>OpenStreetMap</a>プロジェクトのデータを使用します。</p>",
"overrideAll": {
"tagRenderings+": {
"0": {
"question": "もっと情報のある(非公式の)ウェブサイトはありますか(例えば、topos)?"
},
"4": {
"question": "ルートの(平均)長さはメートル単位でいくつですか?",
"render": "ルートの長さは平均で<b>{canonical(climbing:length)}</b>です"
},
"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": {

View file

@ -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": {

View file

@ -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 <a href='https://utopicode.de/en/?ref=kletterspots' target='_blank'>Christian Neumann</a> op <a href='https://kletterspots.de' target='_blank'>kletterspots.de</a>.",
"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": "<span class='subtle'>Een <a href='#{_embedding_feature:id}'>omvattend element</a> geeft aan dat dit <span>publiek toegangkelijk is</span><br/>{_embedding_feature:access:description}"
},
"1": {
"then": "<span class='subtle'>Een <a href='#{_embedding_feature:id}'>omvattend element</a> geeft aan dat</span> een toelating nodig is om hier te klimmen<br/>{_embedding_feature:access:description}"
}
}
},
"4": {
"question": "Wat is de (gemiddelde) lengte van de klimroutes, in meter?",
"render": "De klimroutes zijn gemiddeld <b>{canonical(climbing:length)}</b> 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": "<span class='subtle'>Een <a href='#{_embedding_feature:id}'>omvattend element</a> geeft aan dat dit <span>publiek toegangkelijk is</span><br/>{_embedding_feature:access:description}"
},
"1": {
"then": "<span class='subtle'>Een <a href='#{_embedding_feature:id}'>omvattend element</a> geeft aan dat</span> een toelating nodig is om hier te klimmen<br/>{_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? <br/><span class='subtle'>(Dit is klimmen met klemblokjes en friends)</span>"
},
"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"
}
}
}
}
}

View file

@ -227,34 +227,25 @@
"climbing": {
"description": "На этой карте вы найдете различные возможности для скалолазания, такие как скалодромы, залы для боулдеринга и скалы на природе.",
"descriptionTail": "Создатель карты скалолазания — <a href='https://utopicode.de/en/?ref=kletterspots' target='_blank'>Christian Neumann</a>. Пожалуйста, <a href='https://utopicode.de/en/contact/?project=kletterspots&ref=kletterspots' target='blank'>пишите</a> если у вас есть отзыв или вопросы.</p><p>Проект использует данные <a href='https://www.openstreetmap.org/' target='_blank'>OpenStreetMap</a>.</p>",
"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": " метр"
}
}
}
}
}

View file

@ -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",

View file

@ -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<string>): Map<string, LayerConfigJson> {
@ -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 => {

42
test.ts
View file

@ -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<Translation>(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")
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")