forked from MapComplete/MapComplete
		
	Merge branch 'feature/doctor' of https://github.com/pietervdvn/MapComplete into feature/doctor
This commit is contained in:
		
						commit
						7fd6a89c27
					
				
					 30 changed files with 1625 additions and 241 deletions
				
			
		
							
								
								
									
										14
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -2,7 +2,6 @@ dist/*
 | 
				
			||||||
node_modules
 | 
					node_modules
 | 
				
			||||||
.cache/*
 | 
					.cache/*
 | 
				
			||||||
.idea/*
 | 
					.idea/*
 | 
				
			||||||
.vscode/*
 | 
					 | 
				
			||||||
scratch
 | 
					scratch
 | 
				
			||||||
assets/editor-layer-index.json
 | 
					assets/editor-layer-index.json
 | 
				
			||||||
assets/generated/*
 | 
					assets/generated/*
 | 
				
			||||||
| 
						 | 
					@ -24,3 +23,16 @@ index_*.ts
 | 
				
			||||||
.~lock.*
 | 
					.~lock.*
 | 
				
			||||||
*.doctest.ts
 | 
					*.doctest.ts
 | 
				
			||||||
service-worker.js
 | 
					service-worker.js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vscode/*
 | 
				
			||||||
 | 
					!.vscode/settings.json
 | 
				
			||||||
 | 
					!.vscode/tasks.json
 | 
				
			||||||
 | 
					!.vscode/launch.json
 | 
				
			||||||
 | 
					!.vscode/extensions.json
 | 
				
			||||||
 | 
					!.vscode/*.code-snippets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Local History for Visual Studio Code
 | 
				
			||||||
 | 
					.history/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Built Visual Studio Code Extensions
 | 
				
			||||||
 | 
					*.vsix
 | 
				
			||||||
							
								
								
									
										14
									
								
								.gitpod.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								.gitpod.yml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,14 @@
 | 
				
			||||||
 | 
					tasks:
 | 
				
			||||||
 | 
					  - init: npm run init
 | 
				
			||||||
 | 
					    command: npm run start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ports:
 | 
				
			||||||
 | 
					  - name: MapComplete Website
 | 
				
			||||||
 | 
					    port: 1234
 | 
				
			||||||
 | 
					    onOpen: open-browser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					vscode:
 | 
				
			||||||
 | 
					  extensions:
 | 
				
			||||||
 | 
					    - "esbenp.prettier-vscode"
 | 
				
			||||||
 | 
					    - "eamodio.gitlens",
 | 
				
			||||||
 | 
					    - "GitHub.vscode-pull-request-github"
 | 
				
			||||||
							
								
								
									
										7
									
								
								.vscode/extensions.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.vscode/extensions.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    "recommendations": [
 | 
				
			||||||
 | 
					        "esbenp.prettier-vscode",
 | 
				
			||||||
 | 
					        "eamodio.gitlens",
 | 
				
			||||||
 | 
					        "GitHub.vscode-pull-request-github"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    "json.schemas": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "fileMatch": [
 | 
				
			||||||
 | 
					                "/assets/layers/*/*.json",
 | 
				
			||||||
 | 
					                "!/assets/layers/*/license_info.json"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "url": "./Docs/Schemas/LayerConfigJson.schema.json"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "fileMatch": [
 | 
				
			||||||
 | 
					                "/assets/themes/*/*.json",
 | 
				
			||||||
 | 
					                "!/assets/themes/*/license_info.json"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "url": "./Docs/Schemas/LayoutConfigJson.schema.json"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "editor.tabSize": 2,
 | 
				
			||||||
 | 
					    "files.autoSave": "onFocusChange",
 | 
				
			||||||
 | 
					    "search.useIgnoreFiles": true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
							
								
								
									
										14
									
								
								.vscode/tasks.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								.vscode/tasks.json
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,14 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "version": "2.0.0",
 | 
				
			||||||
 | 
					  "tasks": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "type": "npm",
 | 
				
			||||||
 | 
					      "script": "start",
 | 
				
			||||||
 | 
					      "path": "/",
 | 
				
			||||||
 | 
					      "group": "build",
 | 
				
			||||||
 | 
					      "problemMatcher": [],
 | 
				
			||||||
 | 
					      "label": "MapComplete Dev",
 | 
				
			||||||
 | 
					      "detail": "Run MapComplete Dev Server"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -73,6 +73,11 @@ To use the WSL in Visual Studio Code:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To use WSL without Visual Studio Code you can replace steps 7 and 8 by opening up a WSL terminal
 | 
					To use WSL without Visual Studio Code you can replace steps 7 and 8 by opening up a WSL terminal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					On mac
 | 
				
			||||||
 | 
					------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Install the `Command line tools for XCode which you can find [here](https://developer.apple.com/download/all/). You might need an apple dev account for this.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Automatic deployment
 | 
					Automatic deployment
 | 
				
			||||||
--------------------
 | 
					--------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,6 +39,14 @@ export abstract class Conversion<TIn, TOut> {
 | 
				
			||||||
        return DesugaringStep.strict(fixed)
 | 
					        return DesugaringStep.strict(fixed)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public convertJoin(json: TIn, context: string, errors: string[], warnings?: string[], information?: string[]): TOut {
 | 
				
			||||||
 | 
					        const fixed = this.convert(json, context)
 | 
				
			||||||
 | 
					        errors?.push(...(fixed.errors ?? []))
 | 
				
			||||||
 | 
					        warnings?.push(...(fixed.warnings ?? []))
 | 
				
			||||||
 | 
					        information?.push(...(fixed.information ?? []))
 | 
				
			||||||
 | 
					        return fixed.result
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public andThenF<X>(f: (tout:TOut) => X ): Conversion<TIn, X>{
 | 
					    public andThenF<X>(f: (tout:TOut) => X ): Conversion<TIn, X>{
 | 
				
			||||||
        return new Pipe(
 | 
					        return new Pipe(
 | 
				
			||||||
            this,
 | 
					            this,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,19 +45,67 @@ class ValidateLanguageCompleteness extends DesugaringStep<any> {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class DoesImageExist extends DesugaringStep<string> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private readonly _knownImagePaths: Set<string>;
 | 
				
			||||||
 | 
					    private readonly doesPathExist: (path: string) => boolean = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(knownImagePaths: Set<string>, checkExistsSync: (path: string) => boolean = undefined) {
 | 
				
			||||||
 | 
					        super("Checks if an image exists", [], "DoesImageExist");
 | 
				
			||||||
 | 
					        this._knownImagePaths = knownImagePaths;
 | 
				
			||||||
 | 
					        this.doesPathExist = checkExistsSync;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    convert(image: string, context: string): { result: string; errors?: string[]; warnings?: string[]; information?: string[] } {
 | 
				
			||||||
 | 
					        const errors = []
 | 
				
			||||||
 | 
					        const warnings = []
 | 
				
			||||||
 | 
					        const information = []
 | 
				
			||||||
 | 
					        if (image.indexOf("{") >= 0) {
 | 
				
			||||||
 | 
					            information.push("Ignoring image with { in the path: " + image)
 | 
				
			||||||
 | 
					            return {result: image}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (image === "assets/SocialImage.png") {
 | 
				
			||||||
 | 
					            return {result: image}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (image.match(/[a-z]*/)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (Svg.All[image + ".svg"] !== undefined) {
 | 
				
			||||||
 | 
					                // This is a builtin img, e.g. 'checkmark' or 'crosshair'
 | 
				
			||||||
 | 
					                return {result: image};
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this._knownImagePaths !== undefined && !this._knownImagePaths.has(image)) {
 | 
				
			||||||
 | 
					            if (this.doesPathExist === undefined) {
 | 
				
			||||||
 | 
					                errors.push(`Image with path ${image} not found or not attributed; it is used in ${context}`)
 | 
				
			||||||
 | 
					            } else if (!this.doesPathExist(image)) {
 | 
				
			||||||
 | 
					                errors.push(`Image with path ${image} does not exist; it is used in ${context}.\n     Check for typo's and missing directories in the path.`)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                errors.push(`Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info`)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            result: image,
 | 
				
			||||||
 | 
					            errors, warnings, information
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
					class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The paths where this layer is originally saved. Triggers some extra checks
 | 
					     * The paths where this layer is originally saved. Triggers some extra checks
 | 
				
			||||||
     * @private
 | 
					     * @private
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private readonly _path?: string;
 | 
					    private readonly _path?: string;
 | 
				
			||||||
    private readonly knownImagePaths: Set<string>;
 | 
					 | 
				
			||||||
    private readonly _isBuiltin: boolean;
 | 
					    private readonly _isBuiltin: boolean;
 | 
				
			||||||
    private _sharedTagRenderings: Map<string, any>;
 | 
					    private _sharedTagRenderings: Map<string, any>;
 | 
				
			||||||
 | 
					    private readonly _validateImage: DesugaringStep<string>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean, sharedTagRenderings: Map<string, any>) {
 | 
					    constructor(doesImageExist: DoesImageExist, path: string, isBuiltin: boolean, sharedTagRenderings: Map<string, any>) {
 | 
				
			||||||
        super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme");
 | 
					        super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme");
 | 
				
			||||||
        this.knownImagePaths = knownImagePaths;
 | 
					        this._validateImage = doesImageExist;
 | 
				
			||||||
        this._path = path;
 | 
					        this._path = path;
 | 
				
			||||||
        this._isBuiltin = isBuiltin;
 | 
					        this._isBuiltin = isBuiltin;
 | 
				
			||||||
        this._sharedTagRenderings = sharedTagRenderings;
 | 
					        this._sharedTagRenderings = sharedTagRenderings;
 | 
				
			||||||
| 
						 | 
					@ -89,26 +137,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
				
			||||||
                errors.push("Found a remote image: " + remoteImage + " in theme " + json.id + ", please download it.")
 | 
					                errors.push("Found a remote image: " + remoteImage + " in theme " + json.id + ", please download it.")
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            for (const image of images) {
 | 
					            for (const image of images) {
 | 
				
			||||||
                if (image.indexOf("{") >= 0) {
 | 
					                this._validateImage.convertJoin(image, context === undefined ? "" : ` in a layer defined in the theme ${context}`, errors, warnings, information)
 | 
				
			||||||
                    information.push("Ignoring image with { in the path: " + image)
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (image === "assets/SocialImage.png") {
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (image.match(/[a-z]*/)) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (Svg.All[image + ".svg"] !== undefined) {
 | 
					 | 
				
			||||||
                        // This is a builtin img, e.g. 'checkmark' or 'crosshair'
 | 
					 | 
				
			||||||
                        continue;// =>
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (this.knownImagePaths !== undefined && !this.knownImagePaths.has(image)) {
 | 
					 | 
				
			||||||
                    const ctx = context === undefined ? "" : ` in a layer defined in the theme ${context}`
 | 
					 | 
				
			||||||
                    errors.push(`Image with path ${image} not found or not attributed; it is used in ${json.id}${ctx}`)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (json.icon.endsWith(".svg")) {
 | 
					            if (json.icon.endsWith(".svg")) {
 | 
				
			||||||
| 
						 | 
					@ -150,9 +179,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
				
			||||||
            if (theme.id !== filename) {
 | 
					            if (theme.id !== filename) {
 | 
				
			||||||
                errors.push("Theme ids should be the same as the name.json, but we got id: " + theme.id + " and filename " + filename + " (" + this._path + ")")
 | 
					                errors.push("Theme ids should be the same as the name.json, but we got id: " + theme.id + " and filename " + filename + " (" + this._path + ")")
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (!this.knownImagePaths.has(theme.icon)) {
 | 
					            this._validateImage.convertJoin(theme.icon, context + ".icon", errors, warnings, information);
 | 
				
			||||||
                errors.push("The theme image " + theme.icon + " is not attributed or not saved locally")
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            const dups = Utils.Dupiclates(json.layers.map(layer => layer["id"]))
 | 
					            const dups = Utils.Dupiclates(json.layers.map(layer => layer["id"]))
 | 
				
			||||||
            if (dups.length > 0) {
 | 
					            if (dups.length > 0) {
 | 
				
			||||||
                errors.push(`The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`)
 | 
					                errors.push(`The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`)
 | 
				
			||||||
| 
						 | 
					@ -166,7 +193,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // The first key in the the title-field must be english, otherwise the title in the loading page will be the different language
 | 
					                // The first key in the the title-field must be english, otherwise the title in the loading page will be the different language
 | 
				
			||||||
                const targetLanguage = theme.title.SupportedLanguages()[0]
 | 
					                const targetLanguage = theme.title.SupportedLanguages()[0]
 | 
				
			||||||
                if(targetLanguage !== "en"){
 | 
					                if (targetLanguage !== "en") {
 | 
				
			||||||
                    warnings.push(`TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key`)
 | 
					                    warnings.push(`TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key`)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -192,10 +219,10 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> {
 | 
					export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> {
 | 
				
			||||||
    constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean, sharedTagRenderings: Map<string, any>) {
 | 
					    constructor(doesImageExist: DoesImageExist, path: string, isBuiltin: boolean, sharedTagRenderings: Map<string, any>) {
 | 
				
			||||||
        super("Validates a theme and the contained layers",
 | 
					        super("Validates a theme and the contained layers",
 | 
				
			||||||
            new ValidateTheme(knownImagePaths, path, isBuiltin, sharedTagRenderings),
 | 
					            new ValidateTheme(doesImageExist, path, isBuiltin, sharedTagRenderings),
 | 
				
			||||||
            new On("layers", new Each(new ValidateLayer(undefined, false, knownImagePaths)))
 | 
					            new On("layers", new Each(new ValidateLayer(undefined, false, doesImageExist)))
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -354,7 +381,7 @@ export class DetectShadowedMappings extends DesugaringStep<QuestionableTagRender
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            for (let j = 0; j < i; j++) {
 | 
					            for (let j = 0; j < i; j++) {
 | 
				
			||||||
                const doesMatch = parsedConditions[j].matchesProperties(properties)
 | 
					                const doesMatch = parsedConditions[j].matchesProperties(properties)
 | 
				
			||||||
                if(doesMatch && json.mappings[j].hideInAnswer === true && json.mappings[i].hideInAnswer !== true){
 | 
					                if (doesMatch && json.mappings[j].hideInAnswer === true && json.mappings[i].hideInAnswer !== true) {
 | 
				
			||||||
                    warnings.push(`At ${context}: Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.`)
 | 
					                    warnings.push(`At ${context}: Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.`)
 | 
				
			||||||
                } else if (doesMatch) {
 | 
					                } else if (doesMatch) {
 | 
				
			||||||
                    // The current mapping is shadowed!
 | 
					                    // The current mapping is shadowed!
 | 
				
			||||||
| 
						 | 
					@ -385,14 +412,15 @@ export class DetectShadowedMappings extends DesugaringStep<QuestionableTagRender
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJson> {
 | 
					export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJson> {
 | 
				
			||||||
    private knownImagePaths: Set<string>;
 | 
					    private readonly _doesImageExist: DoesImageExist;
 | 
				
			||||||
    constructor(knownImagePaths: Set<string>) {
 | 
					
 | 
				
			||||||
 | 
					    constructor(doesImageExist: DoesImageExist) {
 | 
				
			||||||
        super("Checks that 'then'clauses in mappings don't have images, but use 'icon' instead", [], "DetectMappingsWithImages");
 | 
					        super("Checks that 'then'clauses in mappings don't have images, but use 'icon' instead", [], "DetectMappingsWithImages");
 | 
				
			||||||
        this.knownImagePaths = knownImagePaths;
 | 
					        this._doesImageExist = doesImageExist;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * const r = new DetectMappingsWithImages(new Set<string>()).convert({
 | 
					     * const r = new DetectMappingsWithImages(new DoesImageExist(new Set<string>())).convert({
 | 
				
			||||||
     *     "mappings": [
 | 
					     *     "mappings": [
 | 
				
			||||||
     *         {
 | 
					     *         {
 | 
				
			||||||
     *             "if": "bicycle_parking=stands",
 | 
					     *             "if": "bicycle_parking=stands",
 | 
				
			||||||
| 
						 | 
					@ -412,9 +440,9 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
 | 
				
			||||||
     * r.errors.some(msg => msg.indexOf("./assets/layers/bike_parking/staple.svg") >= 0) // => true
 | 
					     * r.errors.some(msg => msg.indexOf("./assets/layers/bike_parking/staple.svg") >= 0) // => true
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[], information?: string[] } {
 | 
					    convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[], information?: string[] } {
 | 
				
			||||||
        const errors = []
 | 
					        const errors: string[] = []
 | 
				
			||||||
        const warnings = []
 | 
					        const warnings: string[] = []
 | 
				
			||||||
        const information = []
 | 
					        const information: string[] = []
 | 
				
			||||||
        if (json.mappings === undefined || json.mappings.length === 0) {
 | 
					        if (json.mappings === undefined || json.mappings.length === 0) {
 | 
				
			||||||
            return {result: json}
 | 
					            return {result: json}
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -432,10 +460,8 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
 | 
				
			||||||
                    information.push(`${ctx}: Ignored image ${images.join(", ")} in 'then'-clause of a mapping as this check has been disabled`)
 | 
					                    information.push(`${ctx}: Ignored image ${images.join(", ")} in 'then'-clause of a mapping as this check has been disabled`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    for (const image of images) {
 | 
					                    for (const image of images) {
 | 
				
			||||||
                        if (this.knownImagePaths !== undefined && !this.knownImagePaths.has(image)) {
 | 
					                        this._doesImageExist.convertJoin(image, ctx, errors, warnings, information);
 | 
				
			||||||
                            const ctx = context === undefined ? "" : ` in a layer defined in the theme ${context}`
 | 
					
 | 
				
			||||||
                            errors.push(`Image with path ${image} not found or not attributed; it is used in ${json.id}${ctx}`)
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
| 
						 | 
					@ -454,10 +480,10 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
 | 
					export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
 | 
				
			||||||
    constructor(layerConfig?: LayerConfigJson, knownImagePaths?: Set<string>) {
 | 
					    constructor(layerConfig?: LayerConfigJson, doesImageExist?: DoesImageExist) {
 | 
				
			||||||
        super("Various validation on tagRenderingConfigs",
 | 
					        super("Various validation on tagRenderingConfigs",
 | 
				
			||||||
            new DetectShadowedMappings(layerConfig),
 | 
					            new DetectShadowedMappings(layerConfig),
 | 
				
			||||||
            new DetectMappingsWithImages(knownImagePaths)
 | 
					            new DetectMappingsWithImages(doesImageExist)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -469,13 +495,13 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private readonly _path?: string;
 | 
					    private readonly _path?: string;
 | 
				
			||||||
    private readonly _isBuiltin: boolean;
 | 
					    private readonly _isBuiltin: boolean;
 | 
				
			||||||
    private knownImagePaths: Set<string> | undefined;
 | 
					    private readonly _doesImageExist: DoesImageExist;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(path: string, isBuiltin: boolean, knownImagePaths: Set<string>) {
 | 
					    constructor(path: string, isBuiltin: boolean, doesImageExist: DoesImageExist) {
 | 
				
			||||||
        super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer");
 | 
					        super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer");
 | 
				
			||||||
        this._path = path;
 | 
					        this._path = path;
 | 
				
			||||||
        this._isBuiltin = isBuiltin;
 | 
					        this._isBuiltin = isBuiltin;
 | 
				
			||||||
        this.knownImagePaths = knownImagePaths
 | 
					        this._doesImageExist = doesImageExist
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    convert(json: LayerConfigJson, context: string): { result: LayerConfigJson; errors: string[]; warnings?: string[], information?: string[] } {
 | 
					    convert(json: LayerConfigJson, context: string): { result: LayerConfigJson; errors: string[]; warnings?: string[], information?: string[] } {
 | 
				
			||||||
| 
						 | 
					@ -563,7 +589,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (json.tagRenderings !== undefined) {
 | 
					            if (json.tagRenderings !== undefined) {
 | 
				
			||||||
                const r = new On("tagRenderings", new Each(new ValidateTagRenderings(json, this.knownImagePaths))).convert(json, context)
 | 
					                const r = new On("tagRenderings", new Each(new ValidateTagRenderings(json, this._doesImageExist))).convert(json, context)
 | 
				
			||||||
                warnings.push(...(r.warnings ?? []))
 | 
					                warnings.push(...(r.warnings ?? []))
 | 
				
			||||||
                errors.push(...(r.errors ?? []))
 | 
					                errors.push(...(r.errors ?? []))
 | 
				
			||||||
                information.push(...(r.information ?? []))
 | 
					                information.push(...(r.information ?? []))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,7 @@ import {FixedUiElement} from "../../UI/Base/FixedUiElement";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class LayerConfig extends WithContextLoader {
 | 
					export default class LayerConfig extends WithContextLoader {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static readonly syncSelectionAllowed = ["no", "local", "theme-only", "global"] as const;
 | 
				
			||||||
    public readonly id: string;
 | 
					    public readonly id: string;
 | 
				
			||||||
    public readonly name: Translation;
 | 
					    public readonly name: Translation;
 | 
				
			||||||
    public readonly description: Translation;
 | 
					    public readonly description: Translation;
 | 
				
			||||||
| 
						 | 
					@ -44,10 +45,8 @@ export default class LayerConfig extends WithContextLoader {
 | 
				
			||||||
    public readonly maxzoom: number;
 | 
					    public readonly maxzoom: number;
 | 
				
			||||||
    public readonly title?: TagRenderingConfig;
 | 
					    public readonly title?: TagRenderingConfig;
 | 
				
			||||||
    public readonly titleIcons: TagRenderingConfig[];
 | 
					    public readonly titleIcons: TagRenderingConfig[];
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public readonly mapRendering: PointRenderingConfig[]
 | 
					    public readonly mapRendering: PointRenderingConfig[]
 | 
				
			||||||
    public readonly lineRendering: LineRenderingConfig[]
 | 
					    public readonly lineRendering: LineRenderingConfig[]
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public readonly units: Unit[];
 | 
					    public readonly units: Unit[];
 | 
				
			||||||
    public readonly deletion: DeleteConfig | null;
 | 
					    public readonly deletion: DeleteConfig | null;
 | 
				
			||||||
    public readonly allowMove: MoveConfig | null
 | 
					    public readonly allowMove: MoveConfig | null
 | 
				
			||||||
| 
						 | 
					@ -57,15 +56,11 @@ export default class LayerConfig extends WithContextLoader {
 | 
				
			||||||
     * In seconds
 | 
					     * In seconds
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public readonly maxAgeOfCache: number
 | 
					    public readonly maxAgeOfCache: number
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public readonly presets: PresetConfig[];
 | 
					    public readonly presets: PresetConfig[];
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public readonly tagRenderings: TagRenderingConfig[];
 | 
					    public readonly tagRenderings: TagRenderingConfig[];
 | 
				
			||||||
    public readonly filters: FilterConfig[];
 | 
					    public readonly filters: FilterConfig[];
 | 
				
			||||||
    public readonly filterIsSameAs: string;
 | 
					    public readonly filterIsSameAs: string;
 | 
				
			||||||
    public readonly forceLoad: boolean;
 | 
					    public readonly forceLoad: boolean;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static readonly syncSelectionAllowed =  ["no" , "local" , "theme-only" , "global"] as const;
 | 
					 | 
				
			||||||
    public readonly syncSelection: (typeof LayerConfig.syncSelectionAllowed)[number] // this is a trick to conver a constant array of strings into a type union of these values
 | 
					    public readonly syncSelection: (typeof LayerConfig.syncSelectionAllowed)[number] // this is a trick to conver a constant array of strings into a type union of these values
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
| 
						 | 
					@ -74,18 +69,24 @@ export default class LayerConfig extends WithContextLoader {
 | 
				
			||||||
        official: boolean = true
 | 
					        official: boolean = true
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        context = context + "." + json.id;
 | 
					        context = context + "." + json.id;
 | 
				
			||||||
        const translationContext = "layers:"+json.id
 | 
					        const translationContext = "layers:" + json.id
 | 
				
			||||||
        super(json, context)
 | 
					        super(json, context)
 | 
				
			||||||
        this.id = json.id;
 | 
					        this.id = json.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (typeof json === "string") {
 | 
				
			||||||
 | 
					            throw `Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed (at ${context})`
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (json.id === undefined) {
 | 
					        if (json.id === undefined) {
 | 
				
			||||||
            throw "Not a valid layer: id is undefined: " + JSON.stringify(json)
 | 
					            throw `Not a valid layer: id is undefined: ${JSON.stringify(json)} (At ${context})`
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (json.source === undefined) {
 | 
					        if (json.source === undefined) {
 | 
				
			||||||
            throw "Layer " + this.id + " does not define a source section (" + context + ")"
 | 
					            throw "Layer " + this.id + " does not define a source section (" + context + ")"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (json.source.osmTags === undefined) {
 | 
					        if (json.source.osmTags === undefined) {
 | 
				
			||||||
            throw "Layer " + this.id + " does not define a osmTags in the source section - these should always be present, even for geojson layers (" + context + ")"
 | 
					            throw "Layer " + this.id + " does not define a osmTags in the source section - these should always be present, even for geojson layers (" + context + ")"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -98,8 +99,8 @@ export default class LayerConfig extends WithContextLoader {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30
 | 
					        this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30
 | 
				
			||||||
        if(json.syncSelection !== undefined && LayerConfig.syncSelectionAllowed.indexOf(json.syncSelection) < 0){
 | 
					        if (json.syncSelection !== undefined && LayerConfig.syncSelectionAllowed.indexOf(json.syncSelection) < 0) {
 | 
				
			||||||
            throw context+ " Invalid sync-selection: must be one of "+LayerConfig.syncSelectionAllowed.map(v => `'${v}'`).join(", ")+" but got '"+json.syncSelection+"'"
 | 
					            throw context + " Invalid sync-selection: must be one of " + LayerConfig.syncSelectionAllowed.map(v => `'${v}'`).join(", ") + " but got '" + json.syncSelection + "'"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.syncSelection = json.syncSelection ?? "no";
 | 
					        this.syncSelection = json.syncSelection ?? "no";
 | 
				
			||||||
        const osmTags = TagUtils.Tag(
 | 
					        const osmTags = TagUtils.Tag(
 | 
				
			||||||
| 
						 | 
					@ -107,8 +108,8 @@ export default class LayerConfig extends WithContextLoader {
 | 
				
			||||||
            context + "source.osmTags"
 | 
					            context + "source.osmTags"
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(Constants.priviliged_layers.indexOf(this.id) < 0 && osmTags.isNegative()){
 | 
					        if (Constants.priviliged_layers.indexOf(this.id) < 0 && osmTags.isNegative()) {
 | 
				
			||||||
            throw context + "The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t"+osmTags.asHumanString(false, false, {});
 | 
					            throw context + "The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" + osmTags.asHumanString(false, false, {});
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (json.source["geoJsonSource"] !== undefined) {
 | 
					        if (json.source["geoJsonSource"] !== undefined) {
 | 
				
			||||||
| 
						 | 
					@ -138,8 +139,8 @@ export default class LayerConfig extends WithContextLoader {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.allowSplit = json.allowSplit ?? false;
 | 
					        this.allowSplit = json.allowSplit ?? false;
 | 
				
			||||||
        this.name = Translations.T(json.name, translationContext + ".name");
 | 
					        this.name = Translations.T(json.name, translationContext + ".name");
 | 
				
			||||||
        if(json.units!==undefined && !Array.isArray(json.units)){
 | 
					        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"
 | 
					            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}]`)))
 | 
					        this.units = (json.units ?? []).map(((unitJson, i) => Unit.fromJson(unitJson, `${context}.unit[${i}]`)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -167,8 +168,8 @@ export default class LayerConfig extends WithContextLoader {
 | 
				
			||||||
                const index = kv.indexOf("=");
 | 
					                const index = kv.indexOf("=");
 | 
				
			||||||
                let key = kv.substring(0, index).trim();
 | 
					                let key = kv.substring(0, index).trim();
 | 
				
			||||||
                const r = "[a-z_][a-z0-9:]*"
 | 
					                const r = "[a-z_][a-z0-9:]*"
 | 
				
			||||||
                if(key.match(r) === null){
 | 
					                if (key.match(r) === null) {
 | 
				
			||||||
                    throw "At "+context+" invalid key for calculated tag: "+key+"; it should match "+r
 | 
					                    throw "At " + context + " invalid key for calculated tag: " + key + "; it should match " + r
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                const isStrict = key.endsWith(':')
 | 
					                const isStrict = key.endsWith(':')
 | 
				
			||||||
                if (isStrict) {
 | 
					                if (isStrict) {
 | 
				
			||||||
| 
						 | 
					@ -343,14 +344,14 @@ export default class LayerConfig extends WithContextLoader {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public GenerateDocumentation(usedInThemes: string[], layerIsNeededBy?: Map<string, string[]>, dependencies: {
 | 
					    public GenerateDocumentation(usedInThemes: string[], layerIsNeededBy?: Map<string, string[]>, dependencies: {
 | 
				
			||||||
        context?: string;
 | 
					                                     context?: string;
 | 
				
			||||||
        reason: string;
 | 
					                                     reason: string;
 | 
				
			||||||
        neededLayer: string;
 | 
					                                     neededLayer: string;
 | 
				
			||||||
    }[] = []
 | 
					                                 }[] = []
 | 
				
			||||||
                                 , addedByDefault = false, canBeIncluded = true): BaseUIElement {
 | 
					        , addedByDefault = false, canBeIncluded = true): BaseUIElement {
 | 
				
			||||||
        const extraProps = []
 | 
					        const extraProps = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        extraProps.push("This layer is shown at zoomlevel **"+this.minzoom+"** and higher")
 | 
					        extraProps.push("This layer is shown at zoomlevel **" + this.minzoom + "** and higher")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (canBeIncluded) {
 | 
					        if (canBeIncluded) {
 | 
				
			||||||
            if (addedByDefault) {
 | 
					            if (addedByDefault) {
 | 
				
			||||||
| 
						 | 
					@ -440,7 +441,7 @@ export default class LayerConfig extends WithContextLoader {
 | 
				
			||||||
        let overpassLink: BaseUIElement = undefined;
 | 
					        let overpassLink: BaseUIElement = undefined;
 | 
				
			||||||
        if (Constants.priviliged_layers.indexOf(this.id) < 0) {
 | 
					        if (Constants.priviliged_layers.indexOf(this.id) < 0) {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                overpassLink = new Link("Execute on overpass", Overpass.AsOverpassTurboLink(<TagsFilter> new And(neededTags).optimize()))
 | 
					                overpassLink = new Link("Execute on overpass", Overpass.AsOverpassTurboLink(<TagsFilter>new And(neededTags).optimize()))
 | 
				
			||||||
            } catch (e) {
 | 
					            } catch (e) {
 | 
				
			||||||
                console.error("Could not generate overpasslink for " + this.id)
 | 
					                console.error("Could not generate overpasslink for " + this.id)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -352,7 +352,7 @@ export default class TagRenderingConfig {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (hideInAnswer !== true && !(mp.ifnot?.isUsableAsAnswer() ?? true)) {
 | 
					            if (hideInAnswer !== true && !(mp.ifnot?.isUsableAsAnswer() ?? true)) {
 | 
				
			||||||
                throw `${context}.mapping[${i}].ifnot: This value cannot be used to answer a question, probably because it contains a regex or an OR. Either change it or set 'hideInAnswer'`
 | 
					                throw `${context}.mapping[${i}].ifnot: This value cannot be used to answer a question, probably because it contains a regex or an OR. If a contributor were to pick this as an option, MapComplete wouldn't be able to determine which tags to add.\n    Either change it or set 'hideInAnswer'`
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										12
									
								
								assets/layers/shelter/license_info.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								assets/layers/shelter/license_info.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					[
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "path": "shelter.svg",
 | 
				
			||||||
 | 
					    "license": "MIT",
 | 
				
			||||||
 | 
					    "authors": [
 | 
				
			||||||
 | 
					      "Diemen Design"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "sources": [
 | 
				
			||||||
 | 
					      "https://icon-icons.com/icon/map-shelter/158301"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
							
								
								
									
										88
									
								
								assets/layers/shelter/shelter.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								assets/layers/shelter/shelter.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,88 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "id": "shelter",
 | 
				
			||||||
 | 
					  "name": {
 | 
				
			||||||
 | 
					    "en": "Shelter"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "description": {
 | 
				
			||||||
 | 
					    "en": "Layer showing shelter structures"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "source": {
 | 
				
			||||||
 | 
					    "osmTags": {
 | 
				
			||||||
 | 
					      "and": [
 | 
				
			||||||
 | 
					        "amenity=shelter"
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "minzoom": 13,
 | 
				
			||||||
 | 
					  "title": {
 | 
				
			||||||
 | 
					    "render": {
 | 
				
			||||||
 | 
					      "en": "Shelter"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "mapRendering": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "location": [
 | 
				
			||||||
 | 
					        "point",
 | 
				
			||||||
 | 
					        "centroid"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "icon": "./assets/layers/shelter/shelter.svg"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "tagRenderings": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "shelter-type",
 | 
				
			||||||
 | 
					      "mappings": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "shelter_type=public_transport",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This is a shelter at a public transport stop."
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "shelter_type=picnic_shelter",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This is a shelter protecting from rain at a picnic site."
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "shelter_type=gazebo",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This is a gazebo."
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "shelter_type=weather_shelter",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This is a small shelter, primarily intended for short breaks. Usually found in the mountains or alongside roads."
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "shelter_type=lean_to",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This is a shed with 3 walls, primarily intended for camping."
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "shelter_type=pavilion",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This is a pavilion"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "shelter_type=basic_hut",
 | 
				
			||||||
 | 
					          "then": "This is a basic hut, providing basic shelter and sleeping facilities."
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "What kind of shelter is this?"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "en": "Shelter type: {shelter_type}"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "freeform": {
 | 
				
			||||||
 | 
					        "key": "shelter_type",
 | 
				
			||||||
 | 
					        "type": "string"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								assets/layers/shelter/shelter.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/layers/shelter/shelter.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					<svg role="img" focusable="false" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14"><path d="M 4.4352679,0.99999571 3.578125,2.7143027 l 0.4017857,0 0.8973214,-1.71430699 -0.4419642,0 z m 3.4285714,0 -0.8571429,1.71430699 0.4017857,0 0.8973215,-1.71430699 -0.4419643,0 z m 3.4285707,0 -0.857143,1.71430699 0.401786,0 0.897322,-1.71430699 -0.441965,0 z M 1.8571429,2.7143027 1,4.4286098 l 0.4017857,0 0.8989955,-1.7143071 -0.4436383,0 z m 3.4352678,0 -0.8571428,1.7143071 0.4017857,0 0.8973214,-1.7143071 -0.4419643,0 z m 3.4285714,0 -0.8571428,1.7143071 0.4017857,0 0.8973214,-1.7143071 -0.4419643,0 z m 3.4285709,0 -0.857143,1.7143071 0.401786,0 0.897322,-1.7143071 -0.441965,0 z m -9.0066959,1.7143071 -0.8571428,1.714307 0.4017857,0 0.8989955,-1.714307 -0.4436384,0 z m 6.842076,0 -0.8571429,1.714307 0.4017857,0 0.8989951,-1.714307 -0.4436379,0 z M 7,5.2857633 l -6,3.428614 1.7142857,0 0.8571429,-0.3431962 0,4.1853199 0,0.01507 a 0.42857143,0.42857675 0 0 0 0.2059152,0.366634 0.42857143,0.42857675 0 0 0 0.4319196,0.0067 0.42857143,0.42857675 0 0 0 0.219308,-0.373322 l 0,-4.5435832 L 7,7.0000703 l 2.5714286,1.0279145 0,4.5435832 a 0.42857143,0.42857675 0 0 0 0.2059148,0.366634 0.42857143,0.42857675 0 0 0 0.4319206,0.0067 0.42857143,0.42857675 0 0 0 0.219307,-0.37332 l 0,-4.2003869 0.857143,0.3431962 1.714286,0 -6,-3.428614 z"/></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 1.3 KiB  | 
							
								
								
									
										141
									
								
								assets/layers/transit_routes/transit_routes.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								assets/layers/transit_routes/transit_routes.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,141 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "id": "transit_routes",
 | 
				
			||||||
 | 
					  "name": {
 | 
				
			||||||
 | 
					    "en": "Bus lines"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "description": {
 | 
				
			||||||
 | 
					    "en": "Layer showing bus lines"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "source": {
 | 
				
			||||||
 | 
					    "osmTags": {
 | 
				
			||||||
 | 
					      "and": [
 | 
				
			||||||
 | 
					        "type=route",
 | 
				
			||||||
 | 
					        "route=bus"
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "minzoom": 15,
 | 
				
			||||||
 | 
					  "title": {
 | 
				
			||||||
 | 
					    "render": {
 | 
				
			||||||
 | 
					      "en": "Bus line"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "mappings": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "if": "name~*",
 | 
				
			||||||
 | 
					        "then": {
 | 
				
			||||||
 | 
					          "en": "{name}"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "mapRendering": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "color": {
 | 
				
			||||||
 | 
					        "render": {
 | 
				
			||||||
 | 
					          "en": "#ff0000"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "mappings": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "if": "colour~*",
 | 
				
			||||||
 | 
					            "then": "{colour}"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "tagRenderings": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "name",
 | 
				
			||||||
 | 
					      "freeform": {
 | 
				
			||||||
 | 
					        "key": "name",
 | 
				
			||||||
 | 
					        "type": "string",
 | 
				
			||||||
 | 
					        "placeholder": "Bus XX: From => Via => To"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "render": "{name}",
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "What is the name for this bus line? (i.e. Bus XX: From => Via => To)"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "from",
 | 
				
			||||||
 | 
					      "freeform": {
 | 
				
			||||||
 | 
					        "key": "from",
 | 
				
			||||||
 | 
					        "type": "string",
 | 
				
			||||||
 | 
					        "placeholder": "City, Stop Name"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "en": "This bus line begins at {from}"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "What is the starting point for this bus line?"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "via",
 | 
				
			||||||
 | 
					      "freeform": {
 | 
				
			||||||
 | 
					        "key": "via",
 | 
				
			||||||
 | 
					        "type": "string",
 | 
				
			||||||
 | 
					        "placeholder": "City, Stop Name"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "en": "This bus line goes via {via}"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "What is the via point for this bus line?"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "to",
 | 
				
			||||||
 | 
					      "freeform": {
 | 
				
			||||||
 | 
					        "key": "to",
 | 
				
			||||||
 | 
					        "type": "string",
 | 
				
			||||||
 | 
					        "placeholder": "City, Stop Name"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "en": "This bus line ends at {to}"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "What is the ending point for this bus line?"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "colour",
 | 
				
			||||||
 | 
					      "freeform": {
 | 
				
			||||||
 | 
					        "key": "colour",
 | 
				
			||||||
 | 
					        "type": "color"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "en": "This bus line has the color {colour}"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "What is the colour for this bus line?"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "network",
 | 
				
			||||||
 | 
					      "freeform": {
 | 
				
			||||||
 | 
					        "key": "network",
 | 
				
			||||||
 | 
					        "type": "string"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "en": "This bus line is part of the {network} network"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "What network does this bus line belong to?"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "operator",
 | 
				
			||||||
 | 
					      "freeform": {
 | 
				
			||||||
 | 
					        "key": "operator",
 | 
				
			||||||
 | 
					        "type": "string"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "en": "This bus line is operated by {operator}"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "What company operates this bus line?"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										55
									
								
								assets/layers/transit_stops/bus_stop.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								assets/layers/transit_stops/bus_stop.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
				
			||||||
 | 
					<svg
 | 
				
			||||||
 | 
					   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
				
			||||||
 | 
					   xmlns:cc="http://creativecommons.org/ns#"
 | 
				
			||||||
 | 
					   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
				
			||||||
 | 
					   xmlns:svg="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					   xmlns="http://www.w3.org/2000/svg"
 | 
				
			||||||
 | 
					   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
				
			||||||
 | 
					   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
				
			||||||
 | 
					   width="500"
 | 
				
			||||||
 | 
					   height="500"
 | 
				
			||||||
 | 
					   viewBox="0 0 500 500"
 | 
				
			||||||
 | 
					   version="1.1"
 | 
				
			||||||
 | 
					   id="svg4"
 | 
				
			||||||
 | 
					   sodipodi:docname="bus_stop.svg"
 | 
				
			||||||
 | 
					   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
 | 
				
			||||||
 | 
					  <metadata
 | 
				
			||||||
 | 
					     id="metadata10">
 | 
				
			||||||
 | 
					    <rdf:RDF>
 | 
				
			||||||
 | 
					      <cc:Work
 | 
				
			||||||
 | 
					         rdf:about="">
 | 
				
			||||||
 | 
					        <dc:format>image/svg+xml</dc:format>
 | 
				
			||||||
 | 
					        <dc:type
 | 
				
			||||||
 | 
					           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
 | 
				
			||||||
 | 
					        <dc:title></dc:title>
 | 
				
			||||||
 | 
					      </cc:Work>
 | 
				
			||||||
 | 
					    </rdf:RDF>
 | 
				
			||||||
 | 
					  </metadata>
 | 
				
			||||||
 | 
					  <defs
 | 
				
			||||||
 | 
					     id="defs8" />
 | 
				
			||||||
 | 
					  <sodipodi:namedview
 | 
				
			||||||
 | 
					     pagecolor="#ffffff"
 | 
				
			||||||
 | 
					     bordercolor="#666666"
 | 
				
			||||||
 | 
					     borderopacity="1"
 | 
				
			||||||
 | 
					     objecttolerance="10"
 | 
				
			||||||
 | 
					     gridtolerance="10"
 | 
				
			||||||
 | 
					     guidetolerance="10"
 | 
				
			||||||
 | 
					     inkscape:pageopacity="0"
 | 
				
			||||||
 | 
					     inkscape:pageshadow="2"
 | 
				
			||||||
 | 
					     inkscape:window-width="1920"
 | 
				
			||||||
 | 
					     inkscape:window-height="1017"
 | 
				
			||||||
 | 
					     id="namedview6"
 | 
				
			||||||
 | 
					     showgrid="false"
 | 
				
			||||||
 | 
					     inkscape:zoom="25.986174"
 | 
				
			||||||
 | 
					     inkscape:cx="-2.629287"
 | 
				
			||||||
 | 
					     inkscape:cy="10.208516"
 | 
				
			||||||
 | 
					     inkscape:window-x="-8"
 | 
				
			||||||
 | 
					     inkscape:window-y="-8"
 | 
				
			||||||
 | 
					     inkscape:window-maximized="1"
 | 
				
			||||||
 | 
					     inkscape:current-layer="svg4" />
 | 
				
			||||||
 | 
					  <path
 | 
				
			||||||
 | 
					     d="M 100,0 50,50 v 400 h 32.8125 c 0,0 5.057445,50 40.6375,50 34.45648,0 39.05,-50 39.05,-50 h 175 c 0,0 5.7171,50 39.05,50 40.82343,0 40.6375,-50 40.6375,-50 H 450 V 50 L 400,0 Z m 50,50 h 200 v 50 H 150 Z M 100,150 H 400 V 300 H 100 Z m 0,200 h 50 v 50 h -50 z m 250,0 h 50 v 50 h -50 z"
 | 
				
			||||||
 | 
					     id="path2"
 | 
				
			||||||
 | 
					     style="stroke-width:50" />
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 1.8 KiB  | 
							
								
								
									
										15
									
								
								assets/layers/transit_stops/license_info.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								assets/layers/transit_stops/license_info.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					[
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    "path": "bus_stop.svg",
 | 
				
			||||||
 | 
					    "license": "CC0",
 | 
				
			||||||
 | 
					    "authors": [
 | 
				
			||||||
 | 
					      "Andy Allan",
 | 
				
			||||||
 | 
					      "Michael Glanznig",
 | 
				
			||||||
 | 
					      "Paul Norman",
 | 
				
			||||||
 | 
					      "Paul Dicker"
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "sources": [
 | 
				
			||||||
 | 
					      "https://github.com/gravitystorm/openstreetmap-carto/blob/master/symbols/highway/bus_stop.svg"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
							
								
								
									
										260
									
								
								assets/layers/transit_stops/transit_stops.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								assets/layers/transit_stops/transit_stops.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,260 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "id": "transit_stops",
 | 
				
			||||||
 | 
					  "name": {
 | 
				
			||||||
 | 
					    "en": "Transit Stops"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "description": {
 | 
				
			||||||
 | 
					    "en": "Layer showing different types of transit stops."
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "source": {
 | 
				
			||||||
 | 
					    "osmTags": {
 | 
				
			||||||
 | 
					      "or": [
 | 
				
			||||||
 | 
					        "highway=bus_stop"
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "minzoom": 15,
 | 
				
			||||||
 | 
					  "title": {
 | 
				
			||||||
 | 
					    "render": {
 | 
				
			||||||
 | 
					      "en": "Transit Stop"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "mappings": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "if": "name~*",
 | 
				
			||||||
 | 
					        "then": {
 | 
				
			||||||
 | 
					          "en": "Stop {name}"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "mapRendering": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "location": [
 | 
				
			||||||
 | 
					        "point",
 | 
				
			||||||
 | 
					        "centroid"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "icon": {
 | 
				
			||||||
 | 
					        "render": "./assets/layers/transit_stops/bus_stop.svg",
 | 
				
			||||||
 | 
					        "mappings": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "label": "<div style=\"background: white; display: block\">{name}</div>"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "calculatedTags": [
 | 
				
			||||||
 | 
					    "_routes=feat.memberships()",
 | 
				
			||||||
 | 
					    "_contained_routes_properties=feat.memberships().map(p => {return {id: p.relation.id, name: p.relation.properties.name} }).filter((v,i,a)=>a.findIndex(t=>(JSON.stringify(t) === JSON.stringify(v)))===i)",
 | 
				
			||||||
 | 
					    "_contained_route_ids=JSON.parse(feat.properties._contained_routes_properties ?? '[]').map(p => p.id)",
 | 
				
			||||||
 | 
					    "_contained_routes=JSON.parse(feat.properties._contained_routes_properties ?? '[]').map(p => `<li><a href='#relation/${p.id}'>${p.name ?? 'bus route'}</a></li>`).join('')",
 | 
				
			||||||
 | 
					    "_contained_routes_count=JSON.parse(feat.properties._contained_routes_properties ?? '[]').length"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "tagRenderings": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "stop_name",
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "en": "This stop is called <b>{name}</b>"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "freeform": {
 | 
				
			||||||
 | 
					        "key": "name",
 | 
				
			||||||
 | 
					        "type": "string",
 | 
				
			||||||
 | 
					        "addExtraTags": [
 | 
				
			||||||
 | 
					          "noname="
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "placeholder": {
 | 
				
			||||||
 | 
					          "en": "Name of the stop"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "mappings": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": {
 | 
				
			||||||
 | 
					            "and": [
 | 
				
			||||||
 | 
					              "noname=yes",
 | 
				
			||||||
 | 
					              "name="
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop has no name"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "What is the name of this stop?"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "placeholder": "Name of the stop"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "images",
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "shelter",
 | 
				
			||||||
 | 
					      "mappings": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "shelter=yes",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop has a shelter"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "shelter=no",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop does not have a shelter"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "shelter=separate",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop has a shelter, that's separately mapped"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "hideInAnswer": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "Does this stop have a shelter?"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "bench",
 | 
				
			||||||
 | 
					      "mappings": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "bench=yes",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop has a bench"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "bench=no",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop does not have a bench"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "bench=separate",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop has a bench, that's separately mapped"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "hideInAnswer": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "Does this stop have a bench?"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "bin",
 | 
				
			||||||
 | 
					      "mappings": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "bin=yes",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop has a bin"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "bin=no",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop does not have a bin"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "bin=separate",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop has a bin, that's separately mapped"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "hideInAnswer": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "Does this stop have a bin?"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "wheelchair-access",
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "tactile_paving",
 | 
				
			||||||
 | 
					      "mappings": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "tactile_paving=yes",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop has tactile paving"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "tactile_paving=no",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop does not have tactile paving"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "Does this stop have tactile paving?"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "lit",
 | 
				
			||||||
 | 
					      "mappings": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "lit=yes",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop is lit"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "lit=no",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop is not lit"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "Is this stop lit?"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "departures_board",
 | 
				
			||||||
 | 
					      "mappings": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "departures_board=yes",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop has a departures board of unknown type"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "hideInAnswer": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "departures_board=realtime",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop has a board showing realtime departure information"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "passenger_information_display=yes",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop has a board showing realtime departure information"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "hideInAnswer": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "departures_board=timetable",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop has a timetable showing regular departures"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "departures_board=interval",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop has a timetable containing just the interval between departures"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "departures_board=no",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "This stop does not have a departures board"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "en": "<h3>{_contained_routes_count} routes stop at this stop</h3> <ul>{_contained_routes}</ul>"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "condition": "_contained_routes~*",
 | 
				
			||||||
 | 
					      "id": "contained_routes"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "filter": [],
 | 
				
			||||||
 | 
					  "allowMove": false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -311,6 +311,10 @@
 | 
				
			||||||
                "if": "theme=toilets",
 | 
					                "if": "theme=toilets",
 | 
				
			||||||
                "then": "./assets/themes/toilets/toilets.svg"
 | 
					                "then": "./assets/themes/toilets/toilets.svg"
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                "if": "theme=transit",
 | 
				
			||||||
 | 
					                "then": "./assets/layers/transit_stops/bus_stop.svg"
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
              {
 | 
					              {
 | 
				
			||||||
                "if": "theme=trees",
 | 
					                "if": "theme=trees",
 | 
				
			||||||
                "then": "./assets/themes/trees/logo.svg"
 | 
					                "then": "./assets/themes/trees/logo.svg"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										51
									
								
								assets/themes/transit/transit.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								assets/themes/transit/transit.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,51 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "id": "transit",
 | 
				
			||||||
 | 
					  "maintainer": "Robin van der Linde",
 | 
				
			||||||
 | 
					  "version": "20220406",
 | 
				
			||||||
 | 
					  "title": {
 | 
				
			||||||
 | 
					    "en": "Bus routes"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "description": {
 | 
				
			||||||
 | 
					    "en": "Plan your trip with the help of the public transport system."
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "icon": "./assets/layers/transit_stops/bus_stop.svg",
 | 
				
			||||||
 | 
					  "startZoom": 20,
 | 
				
			||||||
 | 
					  "startLat": 53.21333,
 | 
				
			||||||
 | 
					  "startLon": 6.56963,
 | 
				
			||||||
 | 
					  "layers": [
 | 
				
			||||||
 | 
					    "transit_stops",
 | 
				
			||||||
 | 
					    "transit_routes",
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "builtin": "bike_parking",
 | 
				
			||||||
 | 
					      "override": {
 | 
				
			||||||
 | 
					        "minzoom": 19,
 | 
				
			||||||
 | 
					        "minzoomVisible": 19
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "builtin": "parking",
 | 
				
			||||||
 | 
					      "override": {
 | 
				
			||||||
 | 
					        "minzoom": 19,
 | 
				
			||||||
 | 
					        "minzoomVisible": 19
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "builtin": "shelter",
 | 
				
			||||||
 | 
					      "override": {
 | 
				
			||||||
 | 
					        "minzoom": 19,
 | 
				
			||||||
 | 
					        "minzoomVisible": 19,
 | 
				
			||||||
 | 
					        "source": {
 | 
				
			||||||
 | 
					          "osmTags": {
 | 
				
			||||||
 | 
					            "and": [
 | 
				
			||||||
 | 
					              "amenity=shelter",
 | 
				
			||||||
 | 
					              "shelter_type=public_transport"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "hideTagRenderingsWithLabels": [
 | 
				
			||||||
 | 
					        "shelter-type"
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -3568,6 +3568,11 @@
 | 
				
			||||||
                "0": {
 | 
					                "0": {
 | 
				
			||||||
                    "explanation": "{title()} has closed down permanently"
 | 
					                    "explanation": "{title()} has closed down permanently"
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "nonDeleteMappings": {
 | 
				
			||||||
 | 
					                "0": {
 | 
				
			||||||
 | 
					                    "then": "This is actually a pub"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "description": "A layer showing restaurants and fast-food amenities (with a special rendering for friteries)",
 | 
					        "description": "A layer showing restaurants and fast-food amenities (with a special rendering for friteries)",
 | 
				
			||||||
| 
						 | 
					@ -5232,6 +5237,39 @@
 | 
				
			||||||
            "render": "School <i>{name}</i>"
 | 
					            "render": "School <i>{name}</i>"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "shelter": {
 | 
				
			||||||
 | 
					        "description": "Layer showing shelter structures",
 | 
				
			||||||
 | 
					        "name": "Shelter",
 | 
				
			||||||
 | 
					        "tagRenderings": {
 | 
				
			||||||
 | 
					            "shelter-type": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "This is a shelter at a public transport stop."
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "This is a shelter protecting from rain at a picnic site."
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "then": "This is a gazebo."
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "3": {
 | 
				
			||||||
 | 
					                        "then": "This is a small shelter, primarily intended for short breaks. Usually found in the mountains or alongside roads."
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "4": {
 | 
				
			||||||
 | 
					                        "then": "This is a shed with 3 walls, primarily intended for camping."
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "5": {
 | 
				
			||||||
 | 
					                        "then": "This is a pavilion"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "What kind of shelter is this?",
 | 
				
			||||||
 | 
					                "render": "Shelter type: {shelter_type}"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "title": {
 | 
				
			||||||
 | 
					            "render": "Shelter"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "shops": {
 | 
					    "shops": {
 | 
				
			||||||
        "deletion": {
 | 
					        "deletion": {
 | 
				
			||||||
            "extraDeleteReasons": {
 | 
					            "extraDeleteReasons": {
 | 
				
			||||||
| 
						 | 
					@ -5279,11 +5317,13 @@
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "tagRenderings": {
 | 
					        "tagRenderings": {
 | 
				
			||||||
 | 
					            "2": {
 | 
				
			||||||
 | 
					                "override": {
 | 
				
			||||||
 | 
					                    "question": "What kind of shop is this?"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
            "shops-name": {
 | 
					            "shops-name": {
 | 
				
			||||||
                "question": "What is the name of this shop?"
 | 
					                "question": "What is the name of this shop?"
 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            "shops-type-from-id": {
 | 
					 | 
				
			||||||
                "question": "What kind of shop is this?"
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "title": {
 | 
					        "title": {
 | 
				
			||||||
| 
						 | 
					@ -5962,6 +6002,169 @@
 | 
				
			||||||
            "render": "Trail"
 | 
					            "render": "Trail"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "transit_routes": {
 | 
				
			||||||
 | 
					        "description": "Layer showing bus lines",
 | 
				
			||||||
 | 
					        "mapRendering": {
 | 
				
			||||||
 | 
					            "0": {
 | 
				
			||||||
 | 
					                "color": {
 | 
				
			||||||
 | 
					                    "render": "#ff0000"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "name": "Bus lines",
 | 
				
			||||||
 | 
					        "tagRenderings": {
 | 
				
			||||||
 | 
					            "colour": {
 | 
				
			||||||
 | 
					                "question": "What is the colour for this bus line?",
 | 
				
			||||||
 | 
					                "render": "This bus line has the color {colour}"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "from": {
 | 
				
			||||||
 | 
					                "question": "What is the starting point for this bus line?",
 | 
				
			||||||
 | 
					                "render": "This bus line begins at {from}"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "name": {
 | 
				
			||||||
 | 
					                "question": "What is the name for this bus line? (i.e. Bus XX: From => Via => To)"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "network": {
 | 
				
			||||||
 | 
					                "question": "What network does this bus line belong to?",
 | 
				
			||||||
 | 
					                "render": "This bus line is part of the {network} network"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "operator": {
 | 
				
			||||||
 | 
					                "question": "What company operates this bus line?",
 | 
				
			||||||
 | 
					                "render": "This bus line is operated by {operator}"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "to": {
 | 
				
			||||||
 | 
					                "question": "What is the ending point for this bus line?",
 | 
				
			||||||
 | 
					                "render": "This bus line ends at {to}"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "via": {
 | 
				
			||||||
 | 
					                "question": "What is the via point for this bus line?",
 | 
				
			||||||
 | 
					                "render": "This bus line goes via {via}"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "title": {
 | 
				
			||||||
 | 
					            "mappings": {
 | 
				
			||||||
 | 
					                "0": {
 | 
				
			||||||
 | 
					                    "then": "{name}"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "render": "Bus line"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "transit_stops": {
 | 
				
			||||||
 | 
					        "description": "Layer showing different types of transit stops.",
 | 
				
			||||||
 | 
					        "name": "Transit Stops",
 | 
				
			||||||
 | 
					        "tagRenderings": {
 | 
				
			||||||
 | 
					            "bench": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "This stop has a bench"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "This stop does not have a bench"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "then": "This stop has a bench, that's separately mapped"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Does this stop have a bench?"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "bin": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "This stop has a bin"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "This stop does not have a bin"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "then": "This stop has a bin, that's separately mapped"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Does this stop have a bin?"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "contained_routes": {
 | 
				
			||||||
 | 
					                "render": "<h3>{_contained_routes_count} routes stop at this stop</h3> <ul>{_contained_routes}</ul>"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "departures_board": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "This stop has a departures board of unknown type"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "This stop has a board showing realtime departure information"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "then": "This stop has a board showing realtime departure information"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "3": {
 | 
				
			||||||
 | 
					                        "then": "This stop has a timetable showing regular departures"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "4": {
 | 
				
			||||||
 | 
					                        "then": "This stop has a timetable containing just the interval between departures"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "5": {
 | 
				
			||||||
 | 
					                        "then": "This stop does not have a departures board"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "lit": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "This stop is lit"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "This stop is not lit"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Is this stop lit?"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "shelter": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "This stop has a shelter"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "This stop does not have a shelter"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "then": "This stop has a shelter, that's separately mapped"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Does this stop have a shelter?"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "stop_name": {
 | 
				
			||||||
 | 
					                "freeform": {
 | 
				
			||||||
 | 
					                    "placeholder": "Name of the stop"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "This stop has no name"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "What is the name of this stop?",
 | 
				
			||||||
 | 
					                "render": "This stop is called <b>{name}</b>"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "tactile_paving": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "This stop has tactile paving"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "This stop does not have tactile paving"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Does this stop have tactile paving?"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "title": {
 | 
				
			||||||
 | 
					            "mappings": {
 | 
				
			||||||
 | 
					                "0": {
 | 
				
			||||||
 | 
					                    "then": "Stop {name}"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "render": "Transit Stop"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "tree_node": {
 | 
					    "tree_node": {
 | 
				
			||||||
        "description": "A layer showing trees",
 | 
					        "description": "A layer showing trees",
 | 
				
			||||||
        "name": "Tree",
 | 
					        "name": "Tree",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1756,6 +1756,218 @@
 | 
				
			||||||
            "render": "Microbiblioteca"
 | 
					            "render": "Microbiblioteca"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "recycling": {
 | 
				
			||||||
 | 
					        "description": "Un livello con i contenitori e centri per la raccolta rifiuti riciclabili",
 | 
				
			||||||
 | 
					        "filter": {
 | 
				
			||||||
 | 
					            "0": {
 | 
				
			||||||
 | 
					                "options": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "question": "Aperto ora"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "1": {
 | 
				
			||||||
 | 
					                "options": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "question": "Tutti i tipi di rifiuti"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di batterie"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di confezioni per bevande"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "3": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di lattine"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "4": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di abiti"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "5": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di olio da cucina"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "6": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di olio da motore"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "7": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di umido"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "8": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di bottiglie di vetro"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "9": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di vetro"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "10": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di giornali"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "11": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di carta"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "12": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di bottiglie di plastica"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "13": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di confezioni di plastica"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "14": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di plastica"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "15": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di rottami metallici"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "16": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di piccoli elettrodomestici"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "17": {
 | 
				
			||||||
 | 
					                        "question": "Riciclo di secco"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "name": "Riciclo",
 | 
				
			||||||
 | 
					        "presets": {
 | 
				
			||||||
 | 
					            "0": {
 | 
				
			||||||
 | 
					                "title": "un contenitore per il riciclo"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "1": {
 | 
				
			||||||
 | 
					                "title": "un centro di riciclo"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "tagRenderings": {
 | 
				
			||||||
 | 
					            "container-location": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "E' un contenitore sotterraneo"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "Questo contenitore è al chiuso"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "then": "Questo contenitore è all'aperto"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Dove si trova questo contenitore?"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "opening_hours": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "24/7"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Quali sono gli orari di apertura di questo impianto di raccolta e riciclo?"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "operator": {
 | 
				
			||||||
 | 
					                "question": "Quale azienda gestisce questo impianto di raccolta e riciclo?",
 | 
				
			||||||
 | 
					                "render": "Questa struttura di raccola e riciclo è gestita da {operator}"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "recycling-accepts": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "Batterie"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "Cartoni per bevande"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "then": "Lattine"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "3": {
 | 
				
			||||||
 | 
					                        "then": "Abiti"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "4": {
 | 
				
			||||||
 | 
					                        "then": "Olio da cucina"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "5": {
 | 
				
			||||||
 | 
					                        "then": "Olio di motore"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "6": {
 | 
				
			||||||
 | 
					                        "then": "Verde"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "7": {
 | 
				
			||||||
 | 
					                        "then": "Umido"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "8": {
 | 
				
			||||||
 | 
					                        "then": "Bottiglie di vetro"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "9": {
 | 
				
			||||||
 | 
					                        "then": "Vetro"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "10": {
 | 
				
			||||||
 | 
					                        "then": "Giornali"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "11": {
 | 
				
			||||||
 | 
					                        "then": "Carta"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "12": {
 | 
				
			||||||
 | 
					                        "then": "Bottiglie di platica"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "13": {
 | 
				
			||||||
 | 
					                        "then": "Confezioni di plastica"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "14": {
 | 
				
			||||||
 | 
					                        "then": "Plastica"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "15": {
 | 
				
			||||||
 | 
					                        "then": "Rottami metallici"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "16": {
 | 
				
			||||||
 | 
					                        "then": "Scarpe"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "17": {
 | 
				
			||||||
 | 
					                        "then": "Piccoli elettrodomestici"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "18": {
 | 
				
			||||||
 | 
					                        "then": "Piccoli elettrodomestici"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "19": {
 | 
				
			||||||
 | 
					                        "then": "Aghi e oggetti appuntiti"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "20": {
 | 
				
			||||||
 | 
					                        "then": "Secco"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Cosa si può riciclare qui?"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "recycling-centre-name": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "Questo centro raccolta e riciclo rifiuti non ha un nome specifico"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Come si chiama questo centro raccolta e riciclo rifiuti?",
 | 
				
			||||||
 | 
					                "render": "Questo centro raccolta e riciclo rifiuti si chiama <b>{name}</b>"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "recycling-type": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "Questo è un contenitore per il riciclo di rifiuti"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "Questo è un centro per la raccola e riciclo di rifiuti"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "then": "Contenitore per lo smaltimento del secco"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Che tipo di raccolta è questo?"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "title": {
 | 
				
			||||||
 | 
					            "mappings": {
 | 
				
			||||||
 | 
					                "0": {
 | 
				
			||||||
 | 
					                    "then": "Centro di riciclo rifiuti"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "1": {
 | 
				
			||||||
 | 
					                    "then": "Centro di riciclo rifiuti"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "2": {
 | 
				
			||||||
 | 
					                    "then": "Contenitore per il riciclo"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "render": "Impianti di riciclo"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "slow_roads": {
 | 
					    "slow_roads": {
 | 
				
			||||||
        "tagRenderings": {
 | 
					        "tagRenderings": {
 | 
				
			||||||
            "slow_roads-surface": {
 | 
					            "slow_roads-surface": {
 | 
				
			||||||
| 
						 | 
					@ -2259,6 +2471,158 @@
 | 
				
			||||||
            "render": "Punto panoramico"
 | 
					            "render": "Punto panoramico"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "waste_basket": {
 | 
				
			||||||
 | 
					        "description": "Questo è un cestino dei rifiuti pubblico, un bidone della spazzatura, dove puoi buttare via la tua spazzatura",
 | 
				
			||||||
 | 
					        "filter": {
 | 
				
			||||||
 | 
					            "0": {
 | 
				
			||||||
 | 
					                "options": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "question": "Tutti i tipi"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "question": "Cestino per sigarette"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "question": "Cestino per medicinali"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "3": {
 | 
				
			||||||
 | 
					                        "question": "Cestino per escrementi dei cani"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "4": {
 | 
				
			||||||
 | 
					                        "question": "Cestino per la spazzatura"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "5": {
 | 
				
			||||||
 | 
					                        "question": "Cestino dei rifiuti per oggetti taglienti"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "6": {
 | 
				
			||||||
 | 
					                        "question": "Cestino per la plastica"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "1": {
 | 
				
			||||||
 | 
					                "options": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "question": "Cestino per rifiuti con dispenser per sacchetti per escrementi dei cani"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "mapRendering": {
 | 
				
			||||||
 | 
					            "0": {
 | 
				
			||||||
 | 
					                "iconSize": {
 | 
				
			||||||
 | 
					                    "mappings": {
 | 
				
			||||||
 | 
					                        "0": {
 | 
				
			||||||
 | 
					                            "then": "Cestino dei rifiuti"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "name": "Cestino dei rifiuti",
 | 
				
			||||||
 | 
					        "presets": {
 | 
				
			||||||
 | 
					            "0": {
 | 
				
			||||||
 | 
					                "title": "un cestino dei rifiuti"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "tagRenderings": {
 | 
				
			||||||
 | 
					            "dispensing_dog_bags": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "Questo cestino ha un distributore di sacchetti per escrementi dei cani"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "Questo cestino <b>non</b> ha un distributore di sacchetti per escrementi dei cani"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "then": "Questo cestino <b>non</b> ha un distributore di sacchetti per escrementi dei cani"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Questo cestino ha un distributore di sacchetti per escrementi dei cani?"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "waste-basket-waste-types": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "Un cestino rifiuti per uso generico"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "Un cestino rifiuti per uso generico"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "then": "Un cestino rifiuti per escrementi di cani"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "3": {
 | 
				
			||||||
 | 
					                        "then": "Un cestino rifiuti per sigarette"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "4": {
 | 
				
			||||||
 | 
					                        "then": "Un cestino rifiuti per medicinali"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "5": {
 | 
				
			||||||
 | 
					                        "then": "Un cestino rifiuti per aghi e altri oggetti appuntiti"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "6": {
 | 
				
			||||||
 | 
					                        "then": "Un cestino rifiuti per la plastica"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Che tipo di cestino dei rifiuti è questo?"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "title": {
 | 
				
			||||||
 | 
					            "render": "Cestino dei rifiuti"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "waste_disposal": {
 | 
				
			||||||
 | 
					        "description": "Cestino per lo smaltimento dei rifiuti, contenitore di dimensioni medio grandi per lo smaltimento dei rifiuti (domestici)",
 | 
				
			||||||
 | 
					        "filter": {
 | 
				
			||||||
 | 
					            "0": {
 | 
				
			||||||
 | 
					                "options": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "question": "Solo accesso pubblico"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "name": "Contenitori per la raccolta differenziata",
 | 
				
			||||||
 | 
					        "presets": {
 | 
				
			||||||
 | 
					            "0": {
 | 
				
			||||||
 | 
					                "description": "Cestino di dimensioni medio-grandi per lo smaltimento dei rifiuti (domestici)",
 | 
				
			||||||
 | 
					                "title": "un raccoglitore per lo smaltimento rifiuti"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "tagRenderings": {
 | 
				
			||||||
 | 
					            "access": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "Questo cestino può essere usato da chiunque"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "Questo cestino è privato"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "then": "Questo cestino è solo per residenti"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Chi può utilizzare questo cestino per lo smaltimento dei rifiuti?",
 | 
				
			||||||
 | 
					                "render": "Accesso: {access}"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "disposal-location": {
 | 
				
			||||||
 | 
					                "mappings": {
 | 
				
			||||||
 | 
					                    "0": {
 | 
				
			||||||
 | 
					                        "then": "Questo è un contenitore sotterraneo"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "1": {
 | 
				
			||||||
 | 
					                        "then": "Questo contenitore è al chiuso"
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "2": {
 | 
				
			||||||
 | 
					                        "then": "Questo contenitore è all'aperto"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "question": "Dove si trova questo contenitore?"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "title": {
 | 
				
			||||||
 | 
					            "render": "Smaltimento rifiuti"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "windturbine": {
 | 
					    "windturbine": {
 | 
				
			||||||
        "name": "pala eolica",
 | 
					        "name": "pala eolica",
 | 
				
			||||||
        "presets": {
 | 
					        "presets": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5183,11 +5183,13 @@
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "tagRenderings": {
 | 
					        "tagRenderings": {
 | 
				
			||||||
 | 
					            "2": {
 | 
				
			||||||
 | 
					                "override": {
 | 
				
			||||||
 | 
					                    "question": "Wat voor soort winkel is dit?"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
            "shops-name": {
 | 
					            "shops-name": {
 | 
				
			||||||
                "question": "Wat is de naam van deze winkel?"
 | 
					                "question": "Wat is de naam van deze winkel?"
 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            "shops-type-from-id": {
 | 
					 | 
				
			||||||
                "question": "Wat voor soort winkel is dit?"
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "title": {
 | 
					        "title": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -953,6 +953,10 @@
 | 
				
			||||||
        "description": "A map of public toilets",
 | 
					        "description": "A map of public toilets",
 | 
				
			||||||
        "title": "Open Toilet Map"
 | 
					        "title": "Open Toilet Map"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "transit": {
 | 
				
			||||||
 | 
					        "description": "Plan your trip with the help of the public transport system.",
 | 
				
			||||||
 | 
					        "title": "Bus routes"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "trees": {
 | 
					    "trees": {
 | 
				
			||||||
        "description": "Map all the trees!",
 | 
					        "description": "Map all the trees!",
 | 
				
			||||||
        "shortDescription": "Map all the trees",
 | 
					        "shortDescription": "Map all the trees",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -577,6 +577,10 @@
 | 
				
			||||||
        "shortDescription": "Mappa tutti gli alberi",
 | 
					        "shortDescription": "Mappa tutti gli alberi",
 | 
				
			||||||
        "title": "Alberi"
 | 
					        "title": "Alberi"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "waste": {
 | 
				
			||||||
 | 
					        "description": "Mappa dei cestini per i rifiuti e i centri di raccolta e riciclo rifiuti.",
 | 
				
			||||||
 | 
					        "title": "Rifiuti"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "waste_basket": {
 | 
					    "waste_basket": {
 | 
				
			||||||
        "description": "In questa cartina troverai i cestini dei rifiuti nei tuoi paraggi. Se manca un cestino, puoi inserirlo tu stesso",
 | 
					        "description": "In questa cartina troverai i cestini dei rifiuti nei tuoi paraggi. Se manca un cestino, puoi inserirlo tu stesso",
 | 
				
			||||||
        "shortDescription": "Una cartina dei cestini dei rifiuti",
 | 
					        "shortDescription": "Una cartina dei cestini dei rifiuti",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,7 @@
 | 
				
			||||||
    "generate:cache:speelplekken": "npm run generate:layeroverview && ts-node scripts/generateCache.ts speelplekken 14 ../MapComplete-data/speelplekken_cache/ 51.20 4.35 51.09 4.56",
 | 
					    "generate:cache:speelplekken": "npm run generate:layeroverview && ts-node scripts/generateCache.ts speelplekken 14 ../MapComplete-data/speelplekken_cache/ 51.20 4.35 51.09 4.56",
 | 
				
			||||||
    "generate:cache:natuurpunt": "npm run generate:layeroverview && ts-node scripts/generateCache.ts natuurpunt 12 ../MapComplete-data/natuurpunt_cache/ 50.40 2.1 51.54 6.4 --generate-point-overview nature_reserve,visitor_information_centre",
 | 
					    "generate:cache:natuurpunt": "npm run generate:layeroverview && ts-node scripts/generateCache.ts natuurpunt 12 ../MapComplete-data/natuurpunt_cache/ 50.40 2.1 51.54 6.4 --generate-point-overview nature_reserve,visitor_information_centre",
 | 
				
			||||||
    "generate:cache:natuurpunt:mini": "ts-node scripts/generateCache.ts natuurpunt 12 ../../git/MapComplete-data/natuurpunt_cache_mini/ 51.00792239979105 4.497699737548828 51.0353492224462554 4.539070129394531 --generate-point-overview nature_reserve,visitor_information_centre",
 | 
					    "generate:cache:natuurpunt:mini": "ts-node scripts/generateCache.ts natuurpunt 12 ../../git/MapComplete-data/natuurpunt_cache_mini/ 51.00792239979105 4.497699737548828 51.0353492224462554 4.539070129394531 --generate-point-overview nature_reserve,visitor_information_centre",
 | 
				
			||||||
    "generate:layeroverview": "ts-node scripts/generateLayerOverview.ts --no-fail",
 | 
					    "generate:layeroverview": "ts-node scripts/generateLayerOverview.ts",
 | 
				
			||||||
    "generate:licenses": "ts-node scripts/generateLicenseInfo.ts --no-fail",
 | 
					    "generate:licenses": "ts-node scripts/generateLicenseInfo.ts --no-fail",
 | 
				
			||||||
    "query:licenses": "ts-node scripts/generateLicenseInfo.ts --query",
 | 
					    "query:licenses": "ts-node scripts/generateLicenseInfo.ts --query",
 | 
				
			||||||
    "generate:report": "cd Docs/Tools && ./compileStats.sh && git commit . -m 'New statistics ands graphs' && git push",
 | 
					    "generate:report": "cd Docs/Tools && ./compileStats.sh && git commit . -m 'New statistics ands graphs' && git push",
 | 
				
			||||||
| 
						 | 
					@ -36,7 +36,7 @@
 | 
				
			||||||
    "generate:schemas": "ts2json-schema -p Models/ThemeConfig/Json/ -o Docs/Schemas/ -t tsconfig.json -R . -m \".*ConfigJson\" && ts-node scripts/fixSchemas.ts ",
 | 
					    "generate:schemas": "ts2json-schema -p Models/ThemeConfig/Json/ -o Docs/Schemas/ -t tsconfig.json -R . -m \".*ConfigJson\" && ts-node scripts/fixSchemas.ts ",
 | 
				
			||||||
    "generate:service-worker": "tsc service-worker.ts && git_hash=$(git rev-parse HEAD) && sed -i \"s/GITHUB-COMMIT/$git_hash/\" service-worker.js",
 | 
					    "generate:service-worker": "tsc service-worker.ts && git_hash=$(git rev-parse HEAD) && sed -i \"s/GITHUB-COMMIT/$git_hash/\" service-worker.js",
 | 
				
			||||||
    "optimize-images": "cd assets/generated/ &&  find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'",
 | 
					    "optimize-images": "cd assets/generated/ &&  find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'",
 | 
				
			||||||
    "reset:layeroverview": "echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json && echo {\\\"layers\\\": []} > ./assets/generated/known_layers.json",
 | 
					    "reset:layeroverview": "echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json && echo {\\\"layers\\\": []} > ./assets/generated/known_layers.json && rm ./asssets/generated/layers/* && rm ./assets/generated/themes/*",
 | 
				
			||||||
    "generate": "mkdir -p ./assets/generated; npm run reset:layeroverview; npm run generate:images; npm run generate:charging-stations; npm run generate:translations; npm run generate:licenses; npm run generate:layeroverview; npm run generate:service-worker",
 | 
					    "generate": "mkdir -p ./assets/generated; npm run reset:layeroverview; npm run generate:images; npm run generate:charging-stations; npm run generate:translations; npm run generate:licenses; npm run generate:layeroverview; npm run generate:service-worker",
 | 
				
			||||||
    "generate:charging-stations": "cd ./assets/layers/charging_station && ts-node csvToJson.ts && cd -",
 | 
					    "generate:charging-stations": "cd ./assets/layers/charging_station && ts-node csvToJson.ts && cd -",
 | 
				
			||||||
    "prepare-deploy": "npm run generate:service-worker && ./scripts/build.sh",
 | 
					    "prepare-deploy": "npm run generate:service-worker && ./scripts/build.sh",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,12 +46,102 @@ export default class ScriptUtils {
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static async DownloadJSON(url: string, headers?: any): Promise<any>{
 | 
					    public static erasableLog(...text) {
 | 
				
			||||||
 | 
					        process.stdout.write("\r " + text.join(" ") + "                \r")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static sleep(ms) {
 | 
				
			||||||
 | 
					        if (ms <= 0) {
 | 
				
			||||||
 | 
					            process.stdout.write("\r                                       \r")
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return new Promise((resolve) => {
 | 
				
			||||||
 | 
					            process.stdout.write("\r Sleeping for " + (ms / 1000) + "s \r")
 | 
				
			||||||
 | 
					            setTimeout(resolve, 1000);
 | 
				
			||||||
 | 
					        }).then(() => ScriptUtils.sleep(ms - 1000));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static getLayerPaths(): string[] {
 | 
				
			||||||
 | 
					        return ScriptUtils.readDirRecSync("./assets/layers")
 | 
				
			||||||
 | 
					            .filter(path => path.indexOf(".json") > 0)
 | 
				
			||||||
 | 
					            .filter(path => path.indexOf(".proto.json") < 0)
 | 
				
			||||||
 | 
					            .filter(path => path.indexOf("license_info.json") < 0)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static getLayerFiles(): { parsed: LayerConfigJson, path: string }[] {
 | 
				
			||||||
 | 
					        return ScriptUtils.readDirRecSync("./assets/layers")
 | 
				
			||||||
 | 
					            .filter(path => path.indexOf(".json") > 0)
 | 
				
			||||||
 | 
					            .filter(path => path.indexOf(".proto.json") < 0)
 | 
				
			||||||
 | 
					            .filter(path => path.indexOf("license_info.json") < 0)
 | 
				
			||||||
 | 
					            .map(path => {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    const contents = readFileSync(path, "UTF8")
 | 
				
			||||||
 | 
					                    if (contents === "") {
 | 
				
			||||||
 | 
					                        throw "The file " + path + " is empty, did you properly save?"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const parsed = JSON.parse(contents);
 | 
				
			||||||
 | 
					                    return {parsed, path}
 | 
				
			||||||
 | 
					                } catch (e) {
 | 
				
			||||||
 | 
					                    console.error("Could not parse file ", "./assets/layers/" + path, "due to ", e)
 | 
				
			||||||
 | 
					                    throw e
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static getThemePaths(): string[] {
 | 
				
			||||||
 | 
					        return ScriptUtils.readDirRecSync("./assets/themes")
 | 
				
			||||||
 | 
					            .filter(path => path.endsWith(".json") && !path.endsWith(".proto.json"))
 | 
				
			||||||
 | 
					            .filter(path => path.indexOf("license_info.json") < 0)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static getThemeFiles(): { parsed: LayoutConfigJson, path: string }[] {
 | 
				
			||||||
 | 
					        return this.getThemePaths()
 | 
				
			||||||
 | 
					            .map(path => {
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    const contents = readFileSync(path, "UTF8");
 | 
				
			||||||
 | 
					                    if (contents === "") {
 | 
				
			||||||
 | 
					                        throw "The file " + path + " is empty, did you properly save?"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    const parsed = JSON.parse(contents);
 | 
				
			||||||
 | 
					                    return {parsed: parsed, path: path}
 | 
				
			||||||
 | 
					                } catch (e) {
 | 
				
			||||||
 | 
					                    console.error("Could not read file ", path, "due to ", e)
 | 
				
			||||||
 | 
					                    throw e
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static TagInfoHistogram(key: string): Promise<{
 | 
				
			||||||
 | 
					        data: { count: number, value: string, fraction: number }[]
 | 
				
			||||||
 | 
					    }> {
 | 
				
			||||||
 | 
					        const url = `https://taginfo.openstreetmap.org/api/4/key/values?key=${key}&filter=all&lang=en&sortname=count&sortorder=desc&page=1&rp=17&qtype=value`
 | 
				
			||||||
 | 
					        return ScriptUtils.DownloadJSON(url)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static async ReadSvg(path: string): Promise<any> {
 | 
				
			||||||
 | 
					        if (!existsSync(path)) {
 | 
				
			||||||
 | 
					            throw "File not found: " + path
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const root = await xml2js.parseStringPromise(readFileSync(path, "UTF8"))
 | 
				
			||||||
 | 
					        return root.svg
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static async ReadSvgSync(path: string, callback: ((svg: any) => void)): Promise<any> {
 | 
				
			||||||
 | 
					        xml2js.parseString(readFileSync(path, "UTF8"), {async: false}, (err, root) => {
 | 
				
			||||||
 | 
					            if (err) {
 | 
				
			||||||
 | 
					                throw err
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            callback(root["svg"]);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static async DownloadJSON(url: string, headers?: any): Promise<any> {
 | 
				
			||||||
        const data = await ScriptUtils.Download(url, headers);
 | 
					        const data = await ScriptUtils.Download(url, headers);
 | 
				
			||||||
        return JSON.parse(data.content)
 | 
					        return JSON.parse(data.content)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static Download(url, headers?: any): Promise<{content: string}> {
 | 
					    private static Download(url, headers?: any): Promise<{ content: string }> {
 | 
				
			||||||
        return new Promise((resolve, reject) => {
 | 
					        return new Promise((resolve, reject) => {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                headers = headers ?? {}
 | 
					                headers = headers ?? {}
 | 
				
			||||||
| 
						 | 
					@ -83,84 +173,4 @@ export default class ScriptUtils {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static erasableLog(...text) {
 | 
					 | 
				
			||||||
        process.stdout.write("\r " + text.join(" ") + "                \r")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static sleep(ms) {
 | 
					 | 
				
			||||||
        if (ms <= 0) {
 | 
					 | 
				
			||||||
            process.stdout.write("\r                                       \r")
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return new Promise((resolve) => {
 | 
					 | 
				
			||||||
            process.stdout.write("\r Sleeping for " + (ms / 1000) + "s \r")
 | 
					 | 
				
			||||||
            setTimeout(resolve, 1000);
 | 
					 | 
				
			||||||
        }).then(() => ScriptUtils.sleep(ms - 1000));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static getLayerFiles(): { parsed: LayerConfigJson, path: string }[] {
 | 
					 | 
				
			||||||
        return ScriptUtils.readDirRecSync("./assets/layers")
 | 
					 | 
				
			||||||
            .filter(path => path.indexOf(".json") > 0)
 | 
					 | 
				
			||||||
            .filter(path => path.indexOf(".proto.json") < 0)
 | 
					 | 
				
			||||||
            .filter(path => path.indexOf("license_info.json") < 0)
 | 
					 | 
				
			||||||
            .map(path => {
 | 
					 | 
				
			||||||
                try {
 | 
					 | 
				
			||||||
                    const contents = readFileSync(path, "UTF8")
 | 
					 | 
				
			||||||
                    if (contents === "") {
 | 
					 | 
				
			||||||
                        throw "The file " + path + " is empty, did you properly save?"
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    const parsed = JSON.parse(contents);
 | 
					 | 
				
			||||||
                    return {parsed, path}
 | 
					 | 
				
			||||||
                } catch (e) {
 | 
					 | 
				
			||||||
                    console.error("Could not parse file ", "./assets/layers/" + path, "due to ", e)
 | 
					 | 
				
			||||||
                    throw e
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static getThemeFiles(): { parsed: LayoutConfigJson, path: string }[] {
 | 
					 | 
				
			||||||
        return ScriptUtils.readDirRecSync("./assets/themes")
 | 
					 | 
				
			||||||
            .filter(path => path.endsWith(".json") && !path.endsWith(".proto.json"))
 | 
					 | 
				
			||||||
            .filter(path => path.indexOf("license_info.json") < 0)
 | 
					 | 
				
			||||||
            .map(path => {
 | 
					 | 
				
			||||||
                try {
 | 
					 | 
				
			||||||
                    const contents = readFileSync(path, "UTF8");
 | 
					 | 
				
			||||||
                    if (contents === "") {
 | 
					 | 
				
			||||||
                        throw "The file " + path + " is empty, did you properly save?"
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    const parsed = JSON.parse(contents);
 | 
					 | 
				
			||||||
                    return {parsed: parsed, path: path}
 | 
					 | 
				
			||||||
                } catch (e) {
 | 
					 | 
				
			||||||
                    console.error("Could not read file ", path, "due to ", e)
 | 
					 | 
				
			||||||
                    throw e
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static TagInfoHistogram(key: string): Promise<{
 | 
					 | 
				
			||||||
        data: { count: number, value: string, fraction: number }[]
 | 
					 | 
				
			||||||
    }> {
 | 
					 | 
				
			||||||
        const url = `https://taginfo.openstreetmap.org/api/4/key/values?key=${key}&filter=all&lang=en&sortname=count&sortorder=desc&page=1&rp=17&qtype=value`
 | 
					 | 
				
			||||||
        return ScriptUtils.DownloadJSON(url)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async ReadSvg(path: string): Promise<any>{
 | 
					 | 
				
			||||||
        if(!existsSync(path)){
 | 
					 | 
				
			||||||
            throw "File not found: "+path
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        const root =  await xml2js.parseStringPromise(readFileSync(path, "UTF8"))
 | 
					 | 
				
			||||||
        return root.svg
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public static async ReadSvgSync(path: string, callback: ((svg: any) => void)): Promise<any>{
 | 
					 | 
				
			||||||
         xml2js.parseString(readFileSync(path, "UTF8"),{async: false} , (err, root) => {
 | 
					 | 
				
			||||||
             if(err){
 | 
					 | 
				
			||||||
                 throw err
 | 
					 | 
				
			||||||
             }
 | 
					 | 
				
			||||||
             callback(root["svg"]);
 | 
					 | 
				
			||||||
         })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,9 +10,10 @@ mkdir dist 2> /dev/null
 | 
				
			||||||
mkdir dist/assets 2> /dev/null
 | 
					mkdir dist/assets 2> /dev/null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# This script ends every line with '&&' to chain everything. A failure will thus stop the build
 | 
					# This script ends every line with '&&' to chain everything. A failure will thus stop the build
 | 
				
			||||||
npm run generate:editor-layer-index 
 | 
					npm run generate:editor-layer-index &&
 | 
				
			||||||
 | 
					npm run reset:layeroverview
 | 
				
			||||||
npm run generate &&
 | 
					npm run generate &&
 | 
				
			||||||
npm run generate:layeroverview && # generate:layeroverview has to be run twice: the personal theme won't pick up all the layers otherwise
 | 
					npm run generate:layeroverview --force && # generate:layeroverview has to be run twice: the personal theme won't pick up all the layers otherwise; first time happens in 'npm run generate'
 | 
				
			||||||
npm run test &&
 | 
					npm run test &&
 | 
				
			||||||
npm run generate:layouts 
 | 
					npm run generate:layouts 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,11 @@
 | 
				
			||||||
import ScriptUtils from "./ScriptUtils";
 | 
					import ScriptUtils from "./ScriptUtils";
 | 
				
			||||||
import {existsSync, mkdirSync, readFileSync, writeFileSync} from "fs";
 | 
					import {existsSync, mkdirSync, readFileSync, statSync, writeFileSync} from "fs";
 | 
				
			||||||
import * as licenses from "../assets/generated/license_info.json"
 | 
					import * as licenses from "../assets/generated/license_info.json"
 | 
				
			||||||
import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
 | 
					import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
 | 
				
			||||||
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
 | 
					import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
 | 
				
			||||||
import Constants from "../Models/Constants";
 | 
					import Constants from "../Models/Constants";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					    DoesImageExist,
 | 
				
			||||||
    PrevalidateTheme,
 | 
					    PrevalidateTheme,
 | 
				
			||||||
    ValidateLayer,
 | 
					    ValidateLayer,
 | 
				
			||||||
    ValidateTagRenderings,
 | 
					    ValidateTagRenderings,
 | 
				
			||||||
| 
						 | 
					@ -25,6 +26,51 @@ import {Utils} from "../Utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LayerOverviewUtils {
 | 
					class LayerOverviewUtils {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static readonly layerPath = "./assets/generated/layers/"
 | 
				
			||||||
 | 
					    public static readonly themePath = "./assets/generated/themes/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static publicLayerIdsFrom(themefiles: LayoutConfigJson[]): Set<string> {
 | 
				
			||||||
 | 
					        const publicThemes = [].concat(...themefiles
 | 
				
			||||||
 | 
					            .filter(th => !th.hideFromOverview))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return new Set([].concat(...publicThemes.map(th => this.extractLayerIdsFrom(th))))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static extractLayerIdsFrom(themeFile: LayoutConfigJson, includeInlineLayers = true): string[] {
 | 
				
			||||||
 | 
					        const publicLayerIds = []
 | 
				
			||||||
 | 
					        for (const publicLayer of themeFile.layers) {
 | 
				
			||||||
 | 
					            if (typeof publicLayer === "string") {
 | 
				
			||||||
 | 
					                publicLayerIds.push(publicLayer)
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (publicLayer["builtin"] !== undefined) {
 | 
				
			||||||
 | 
					                const bi = publicLayer["builtin"]
 | 
				
			||||||
 | 
					                if (typeof bi === "string") {
 | 
				
			||||||
 | 
					                    publicLayerIds.push(bi)
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                bi.forEach(id => publicLayerIds.push(id))
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (includeInlineLayers) {
 | 
				
			||||||
 | 
					                publicLayerIds.push(publicLayer["id"])
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return publicLayerIds
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    shouldBeUpdated(sourcefile: string | string[], targetfile: string): boolean {
 | 
				
			||||||
 | 
					        if (!existsSync(targetfile)) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const targetModified = statSync(targetfile).mtime
 | 
				
			||||||
 | 
					        if (typeof sourcefile === "string") {
 | 
				
			||||||
 | 
					            sourcefile = [sourcefile]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sourcefile.some(sourcefile => statSync(sourcefile).mtime > targetModified)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    writeSmallOverview(themes: { id: string, title: any, shortDescription: any, icon: string, hideFromOverview: boolean, mustHaveLanguage: boolean, layers: (LayerConfigJson | string | { builtin })[] }[]) {
 | 
					    writeSmallOverview(themes: { id: string, title: any, shortDescription: any, icon: string, hideFromOverview: boolean, mustHaveLanguage: boolean, layers: (LayerConfigJson | string | { builtin })[] }[]) {
 | 
				
			||||||
        const perId = new Map<string, any>();
 | 
					        const perId = new Map<string, any>();
 | 
				
			||||||
        for (const theme of themes) {
 | 
					        for (const theme of themes) {
 | 
				
			||||||
| 
						 | 
					@ -69,23 +115,23 @@ class LayerOverviewUtils {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    writeTheme(theme: LayoutConfigJson) {
 | 
					    writeTheme(theme: LayoutConfigJson) {
 | 
				
			||||||
        if (!existsSync("./assets/generated/themes")) {
 | 
					        if (!existsSync(LayerOverviewUtils.themePath)) {
 | 
				
			||||||
            mkdirSync("./assets/generated/themes");
 | 
					            mkdirSync(LayerOverviewUtils.themePath);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        writeFileSync(`./assets/generated/themes/${theme.id}.json`, JSON.stringify(theme, null, "  "), "UTF8");
 | 
					        writeFileSync(`${LayerOverviewUtils.themePath}${theme.id}.json`, JSON.stringify(theme, null, "  "), "UTF8");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    writeLayer(layer: LayerConfigJson) {
 | 
					    writeLayer(layer: LayerConfigJson) {
 | 
				
			||||||
        if (!existsSync("./assets/generated/layers")) {
 | 
					        if (!existsSync(LayerOverviewUtils.layerPath)) {
 | 
				
			||||||
            mkdirSync("./assets/generated/layers");
 | 
					            mkdirSync(LayerOverviewUtils.layerPath);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        writeFileSync(`./assets/generated/layers/${layer.id}.json`, JSON.stringify(layer, null, "  "), "UTF8");
 | 
					        writeFileSync(`${LayerOverviewUtils.layerPath}${layer.id}.json`, JSON.stringify(layer, null, "  "), "UTF8");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getSharedTagRenderings(knownImagePaths: Set<string>): Map<string, TagRenderingConfigJson> {
 | 
					    getSharedTagRenderings(doesImageExist: DoesImageExist): Map<string, TagRenderingConfigJson> {
 | 
				
			||||||
        const dict = new Map<string, TagRenderingConfigJson>();
 | 
					        const dict = new Map<string, TagRenderingConfigJson>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const validator = new ValidateTagRenderings(undefined, knownImagePaths);
 | 
					        const validator = new ValidateTagRenderings(undefined, doesImageExist);
 | 
				
			||||||
        for (const key in questions["default"]) {
 | 
					        for (const key in questions["default"]) {
 | 
				
			||||||
            if (key === "id") {
 | 
					            if (key === "id") {
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
| 
						 | 
					@ -93,7 +139,7 @@ class LayerOverviewUtils {
 | 
				
			||||||
            questions[key].id = key;
 | 
					            questions[key].id = key;
 | 
				
			||||||
            questions[key]["source"] = "shared-questions"
 | 
					            questions[key]["source"] = "shared-questions"
 | 
				
			||||||
            const config = <TagRenderingConfigJson>questions[key]
 | 
					            const config = <TagRenderingConfigJson>questions[key]
 | 
				
			||||||
            validator.convertStrict(config, "generate-layer-overview:tagRenderings/questions.json:"+key)
 | 
					            validator.convertStrict(config, "generate-layer-overview:tagRenderings/questions.json:" + key)
 | 
				
			||||||
            dict.set(key, config)
 | 
					            dict.set(key, config)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        for (const key in icons["default"]) {
 | 
					        for (const key in icons["default"]) {
 | 
				
			||||||
| 
						 | 
					@ -104,9 +150,9 @@ class LayerOverviewUtils {
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            icons[key].id = key;
 | 
					            icons[key].id = key;
 | 
				
			||||||
            const config =  <TagRenderingConfigJson>icons[key]
 | 
					            const config = <TagRenderingConfigJson>icons[key]
 | 
				
			||||||
            validator.convertStrict(config, "generate-layer-overview:tagRenderings/icons.json:"+key)
 | 
					            validator.convertStrict(config, "generate-layer-overview:tagRenderings/icons.json:" + key)
 | 
				
			||||||
            dict.set(key,config)
 | 
					            dict.set(key, config)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dict.forEach((value, key) => {
 | 
					        dict.forEach((value, key) => {
 | 
				
			||||||
| 
						 | 
					@ -149,16 +195,18 @@ class LayerOverviewUtils {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    main(args: string[]) {
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
    main(_: string[]) {
 | 
					        const forceReload = args.some(a => a == "--force")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const licensePaths = new Set<string>()
 | 
					        const licensePaths = new Set<string>()
 | 
				
			||||||
        for (const i in licenses) {
 | 
					        for (const i in licenses) {
 | 
				
			||||||
            licensePaths.add(licenses[i].path)
 | 
					            licensePaths.add(licenses[i].path)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        const doesImageExist = new DoesImageExist(licensePaths, existsSync)
 | 
				
			||||||
        const sharedLayers = this.buildLayerIndex(licensePaths);
 | 
					        const sharedLayers = this.buildLayerIndex(doesImageExist, forceReload);
 | 
				
			||||||
        const sharedThemes = this.buildThemeIndex(licensePaths, sharedLayers)
 | 
					        const recompiledThemes : string[] = []
 | 
				
			||||||
 | 
					        const sharedThemes = this.buildThemeIndex(doesImageExist, sharedLayers, recompiledThemes, forceReload)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        writeFileSync("./assets/generated/known_layers_and_themes.json", JSON.stringify({
 | 
					        writeFileSync("./assets/generated/known_layers_and_themes.json", JSON.stringify({
 | 
				
			||||||
            "layers": Array.from(sharedLayers.values()),
 | 
					            "layers": Array.from(sharedLayers.values()),
 | 
				
			||||||
| 
						 | 
					@ -168,7 +216,7 @@ class LayerOverviewUtils {
 | 
				
			||||||
        writeFileSync("./assets/generated/known_layers.json", JSON.stringify({layers: Array.from(sharedLayers.values())}))
 | 
					        writeFileSync("./assets/generated/known_layers.json", JSON.stringify({layers: Array.from(sharedLayers.values())}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {
 | 
					       if(recompiledThemes.length > 0) {
 | 
				
			||||||
            // mapcomplete-changes shows an icon for each corresponding mapcomplete-theme
 | 
					            // mapcomplete-changes shows an icon for each corresponding mapcomplete-theme
 | 
				
			||||||
            const iconsPerTheme =
 | 
					            const iconsPerTheme =
 | 
				
			||||||
                Array.from(sharedThemes.values()).map(th => ({
 | 
					                Array.from(sharedThemes.values()).map(th => ({
 | 
				
			||||||
| 
						 | 
					@ -188,28 +236,42 @@ class LayerOverviewUtils {
 | 
				
			||||||
        console.log(green("All done!"))
 | 
					        console.log(green("All done!"))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private buildLayerIndex(knownImagePaths: Set<string>): Map<string, LayerConfigJson> {
 | 
					    private buildLayerIndex(doesImageExist: DoesImageExist, forceReload: boolean): Map<string, LayerConfigJson> {
 | 
				
			||||||
        // First, we expand and validate all builtin layers. These are written to assets/generated/layers
 | 
					        // First, we expand and validate all builtin layers. These are written to assets/generated/layers
 | 
				
			||||||
        // At the same time, an index of available layers is built.
 | 
					        // At the same time, an index of available layers is built.
 | 
				
			||||||
        console.log("   ---------- VALIDATING BUILTIN LAYERS ---------")
 | 
					        console.log("   ---------- VALIDATING BUILTIN LAYERS ---------")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const sharedTagRenderings = this.getSharedTagRenderings(knownImagePaths);
 | 
					        const sharedTagRenderings = this.getSharedTagRenderings(doesImageExist);
 | 
				
			||||||
        const layerFiles = ScriptUtils.getLayerFiles();
 | 
					 | 
				
			||||||
        const sharedLayers = new Map<string, LayerConfigJson>()
 | 
					        const sharedLayers = new Map<string, LayerConfigJson>()
 | 
				
			||||||
        const state: DesugaringContext = {
 | 
					        const state: DesugaringContext = {
 | 
				
			||||||
            tagRenderings: sharedTagRenderings,
 | 
					            tagRenderings: sharedTagRenderings,
 | 
				
			||||||
            sharedLayers
 | 
					            sharedLayers
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const prepLayer = new PrepareLayer(state);
 | 
					        const prepLayer = new PrepareLayer(state);
 | 
				
			||||||
        for (const sharedLayerJson of layerFiles) {
 | 
					        const skippedLayers: string[] = []
 | 
				
			||||||
            const context = "While building builtin layer " + sharedLayerJson.path
 | 
					        const recompiledLayers: string[] = []
 | 
				
			||||||
            const fixed = prepLayer.convertStrict(sharedLayerJson.parsed, context)
 | 
					        for (const sharedLayerPath of ScriptUtils.getLayerPaths()) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if(fixed.source.osmTags["and"] === undefined){
 | 
					            {
 | 
				
			||||||
 | 
					                const targetPath = LayerOverviewUtils.layerPath + sharedLayerPath.substring(sharedLayerPath.lastIndexOf("/"))
 | 
				
			||||||
 | 
					                if (!forceReload && !this.shouldBeUpdated(sharedLayerPath, targetPath)) {
 | 
				
			||||||
 | 
					                    const sharedLayer = JSON.parse(readFileSync(targetPath, "utf8"))
 | 
				
			||||||
 | 
					                    sharedLayers.set(sharedLayer.id, sharedLayer)
 | 
				
			||||||
 | 
					                    skippedLayers.push(sharedLayer.id)
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const parsed = JSON.parse(readFileSync(sharedLayerPath, "utf8"))
 | 
				
			||||||
 | 
					            const context = "While building builtin layer " + sharedLayerPath
 | 
				
			||||||
 | 
					            const fixed = prepLayer.convertStrict(parsed, context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (fixed.source.osmTags["and"] === undefined) {
 | 
				
			||||||
                fixed.source.osmTags = {"and": [fixed.source.osmTags]}
 | 
					                fixed.source.osmTags = {"and": [fixed.source.osmTags]}
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const validator = new ValidateLayer(sharedLayerJson.path, true, knownImagePaths);
 | 
					            const validator = new ValidateLayer(sharedLayerPath, true, doesImageExist);
 | 
				
			||||||
            validator.convertStrict(fixed, context)
 | 
					            validator.convertStrict(fixed, context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (sharedLayers.has(fixed.id)) {
 | 
					            if (sharedLayers.has(fixed.id)) {
 | 
				
			||||||
| 
						 | 
					@ -217,39 +279,18 @@ class LayerOverviewUtils {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            sharedLayers.set(fixed.id, fixed)
 | 
					            sharedLayers.set(fixed.id, fixed)
 | 
				
			||||||
 | 
					            recompiledLayers.push(fixed.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.writeLayer(fixed)
 | 
					            this.writeLayer(fixed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log("Recompiled layers " + recompiledLayers.join(", ") + " and skipped " + skippedLayers.length + " layers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return sharedLayers;
 | 
					        return sharedLayers;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static publicLayerIdsFrom(themefiles: LayoutConfigJson[]): Set<string> {
 | 
					    private buildThemeIndex(doesImageExist: DoesImageExist, sharedLayers: Map<string, LayerConfigJson>, recompiledThemes: string[], forceReload: boolean): Map<string, LayoutConfigJson> {
 | 
				
			||||||
        const publicLayers = [].concat(...themefiles
 | 
					 | 
				
			||||||
            .filter(th => !th.hideFromOverview)
 | 
					 | 
				
			||||||
            .map(th => th.layers))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const publicLayerIds = new Set<string>()
 | 
					 | 
				
			||||||
        for (const publicLayer of publicLayers) {
 | 
					 | 
				
			||||||
            if (typeof publicLayer === "string") {
 | 
					 | 
				
			||||||
                publicLayerIds.add(publicLayer)
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (publicLayer["builtin"] !== undefined) {
 | 
					 | 
				
			||||||
                const bi = publicLayer["builtin"]
 | 
					 | 
				
			||||||
                if (typeof bi === "string") {
 | 
					 | 
				
			||||||
                    publicLayerIds.add(bi)
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                bi.forEach(id => publicLayerIds.add(id))
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            publicLayerIds.add(publicLayer.id)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return publicLayerIds
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private buildThemeIndex(knownImagePaths: Set<string>, sharedLayers: Map<string, LayerConfigJson>): Map<string, LayoutConfigJson> {
 | 
					 | 
				
			||||||
        console.log("   ---------- VALIDATING BUILTIN THEMES ---------")
 | 
					        console.log("   ---------- VALIDATING BUILTIN THEMES ---------")
 | 
				
			||||||
        const themeFiles = ScriptUtils.getThemeFiles();
 | 
					        const themeFiles = ScriptUtils.getThemeFiles();
 | 
				
			||||||
        const fixed = new Map<string, LayoutConfigJson>();
 | 
					        const fixed = new Map<string, LayoutConfigJson>();
 | 
				
			||||||
| 
						 | 
					@ -258,23 +299,33 @@ class LayerOverviewUtils {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const convertState: DesugaringContext = {
 | 
					        const convertState: DesugaringContext = {
 | 
				
			||||||
            sharedLayers,
 | 
					            sharedLayers,
 | 
				
			||||||
            tagRenderings: this.getSharedTagRenderings(knownImagePaths),
 | 
					            tagRenderings: this.getSharedTagRenderings(doesImageExist),
 | 
				
			||||||
            publicLayers
 | 
					            publicLayers
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const nonDefaultLanguages : {theme: string, language: string}[] = []
 | 
					        const skippedThemes: string[] = []
 | 
				
			||||||
        for (const themeInfo of themeFiles) {
 | 
					        for (const themeInfo of themeFiles) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const themePath = themeInfo.path;
 | 
				
			||||||
            let themeFile = themeInfo.parsed
 | 
					            let themeFile = themeInfo.parsed
 | 
				
			||||||
            const themePath = themeInfo.path
 | 
					
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                const targetPath = LayerOverviewUtils.themePath + "/" + themePath.substring(themePath.lastIndexOf("/"))
 | 
				
			||||||
 | 
					                const usedLayers = Array.from(LayerOverviewUtils.extractLayerIdsFrom(themeFile, false))
 | 
				
			||||||
 | 
					                    .map(id => LayerOverviewUtils.layerPath + id + ".json")
 | 
				
			||||||
 | 
					                if (!forceReload && !this.shouldBeUpdated([themePath, ...usedLayers], targetPath)) {
 | 
				
			||||||
 | 
					                    fixed.set(themeFile.id, JSON.parse(readFileSync(LayerOverviewUtils.themePath+themeFile.id+".json", 'utf8')))
 | 
				
			||||||
 | 
					                    skippedThemes.push(themeFile.id)
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                recompiledThemes.push(themeFile.id)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            new PrevalidateTheme().convertStrict(themeFile, themePath)
 | 
					            new PrevalidateTheme().convertStrict(themeFile, themePath)
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                themeFile = new PrepareTheme(convertState).convertStrict(themeFile, themePath)
 | 
					                themeFile = new PrepareTheme(convertState).convertStrict(themeFile, themePath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (knownImagePaths === undefined) {
 | 
					                new ValidateThemeAndLayers(doesImageExist, themePath, true, convertState.tagRenderings)
 | 
				
			||||||
                    throw "Could not load known images/licenses"
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                new ValidateThemeAndLayers(knownImagePaths, themePath, true, convertState.tagRenderings)
 | 
					 | 
				
			||||||
                    .convertStrict(themeFile, themePath)
 | 
					                    .convertStrict(themeFile, themePath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                this.writeTheme(themeFile)
 | 
					                this.writeTheme(themeFile)
 | 
				
			||||||
| 
						 | 
					@ -293,6 +344,9 @@ class LayerOverviewUtils {
 | 
				
			||||||
                mustHaveLanguage: t.mustHaveLanguage?.length > 0,
 | 
					                mustHaveLanguage: t.mustHaveLanguage?.length > 0,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }));
 | 
					        }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        console.log("Recompiled themes " + recompiledThemes.join(", ") + " and skipped " + skippedThemes.length + " themes")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return fixed;
 | 
					        return fixed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
 | 
				
			||||||
import TagRenderingQuestion from "../../../UI/Popup/TagRenderingQuestion";
 | 
					import TagRenderingQuestion from "../../../UI/Popup/TagRenderingQuestion";
 | 
				
			||||||
import {UIEventSource} from "../../../Logic/UIEventSource";
 | 
					import {UIEventSource} from "../../../Logic/UIEventSource";
 | 
				
			||||||
import { expect } from 'chai';
 | 
					import { expect } from 'chai';
 | 
				
			||||||
 | 
					import Locale from "../../../UI/i18n/Locale";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("TagRenderingQuestion", () => {
 | 
					describe("TagRenderingQuestion", () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,6 +28,7 @@ describe("TagRenderingQuestion", () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("should have a freeform text field with a type explanation", () => {
 | 
					    it("should have a freeform text field with a type explanation", () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Locale.language.setData("en")
 | 
				
			||||||
        const configJson = <TagRenderingConfigJson>{
 | 
					        const configJson = <TagRenderingConfigJson>{
 | 
				
			||||||
            id: "test-tag-rendering",
 | 
					            id: "test-tag-rendering",
 | 
				
			||||||
            question: "Question?",
 | 
					            question: "Question?",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,23 +28,28 @@ function initDownloads(query: string){
 | 
				
			||||||
describe("GenerateCache", () => {
 | 
					describe("GenerateCache", () => {
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
        it("should generate a cached file for the Natuurpunt-theme", async () => {
 | 
					        it("should generate a cached file for the Natuurpunt-theme", async () => {
 | 
				
			||||||
            if (existsSync("/tmp/np-cache")) {
 | 
					            // We use /var/tmp instead of /tmp, as more OS's (such as MAC) have this
 | 
				
			||||||
                ScriptUtils.readDirRecSync("/tmp/np-cache").forEach(p => unlinkSync(p))
 | 
					            const dir = "/var/tmp/"
 | 
				
			||||||
                rmdirSync("/tmp/np-cache")
 | 
					            if(!existsSync(dir)){
 | 
				
			||||||
 | 
					                console.log("Not executing caching test: no temp directory found")
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            mkdirSync("/tmp/np-cache")
 | 
					            if (existsSync(dir+"/np-cache")) {
 | 
				
			||||||
 | 
					                ScriptUtils.readDirRecSync(dir+"np-cache").forEach(p => unlinkSync(p))
 | 
				
			||||||
 | 
					                rmdirSync(dir+"np-cache")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            mkdirSync(dir+"np-cache")
 | 
				
			||||||
            initDownloads(
 | 
					            initDownloads(
 | 
				
			||||||
                "(nwr%5B%22amenity%22%3D%22toilets%22%5D%3Bnwr%5B%22amenity%22%3D%22parking%22%5D%3Bnwr%5B%22amenity%22%3D%22bench%22%5D%3Bnwr%5B%22id%22%3D%22location_track%22%5D%3Bnwr%5B%22id%22%3D%22gps%22%5D%3Bnwr%5B%22information%22%3D%22board%22%5D%3Bnwr%5B%22leisure%22%3D%22picnic_table%22%5D%3Bnwr%5B%22man_made%22%3D%22watermill%22%5D%3Bnwr%5B%22user%3Ahome%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Alocation%22%3D%22yes%22%5D%3Bnwr%5B%22leisure%22%3D%22nature_reserve%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22boundary%22%3D%22protected_area%22%5D%5B%22protect_class%22!%3D%2298%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22information%22%3D%22visitor_centre%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22information%22%3D%22office%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22route%22~%22%5E.*foot.*%24%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22route%22~%22%5E.*hiking.*%24%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22route%22~%22%5E.*bycicle.*%24%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22route%22~%22%5E.*horse.*%24%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22leisure%22%3D%22bird_hide%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22amenity%22%3D%22drinking_water%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3B)%3Bout%20body%3Bout%20meta%3B%3E%3Bout%20skel%20qt%3B"
 | 
					                "(nwr%5B%22amenity%22%3D%22toilets%22%5D%3Bnwr%5B%22amenity%22%3D%22parking%22%5D%3Bnwr%5B%22amenity%22%3D%22bench%22%5D%3Bnwr%5B%22id%22%3D%22location_track%22%5D%3Bnwr%5B%22id%22%3D%22gps%22%5D%3Bnwr%5B%22information%22%3D%22board%22%5D%3Bnwr%5B%22leisure%22%3D%22picnic_table%22%5D%3Bnwr%5B%22man_made%22%3D%22watermill%22%5D%3Bnwr%5B%22user%3Ahome%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Alocation%22%3D%22yes%22%5D%3Bnwr%5B%22leisure%22%3D%22nature_reserve%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22boundary%22%3D%22protected_area%22%5D%5B%22protect_class%22!%3D%2298%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22information%22%3D%22visitor_centre%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22information%22%3D%22office%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22route%22~%22%5E.*foot.*%24%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22route%22~%22%5E.*hiking.*%24%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22route%22~%22%5E.*bycicle.*%24%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22route%22~%22%5E.*horse.*%24%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22leisure%22%3D%22bird_hide%22%5D%5B%22operator%22~%22%5E.*%5BnN%5Datuurpunt.*%24%22%5D%3Bnwr%5B%22amenity%22%3D%22drinking_water%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3B)%3Bout%20body%3Bout%20meta%3B%3E%3Bout%20skel%20qt%3B"
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            await main([
 | 
					            await main([
 | 
				
			||||||
                "natuurpunt",
 | 
					                "natuurpunt",
 | 
				
			||||||
                "12",
 | 
					                "12",
 | 
				
			||||||
                "/tmp/np-cache",
 | 
					                dir+"np-cache",
 | 
				
			||||||
                "51.15423567022531", "3.250579833984375", "51.162821593316934", "3.262810707092285",
 | 
					                "51.15423567022531", "3.250579833984375", "51.162821593316934", "3.262810707092285",
 | 
				
			||||||
                "--generate-point-overview", "nature_reserve,visitor_information_centre"
 | 
					                "--generate-point-overview", "nature_reserve,visitor_information_centre"
 | 
				
			||||||
            ])
 | 
					            ])
 | 
				
			||||||
            await ScriptUtils.sleep(500)
 | 
					            await ScriptUtils.sleep(500)
 | 
				
			||||||
            const birdhides = JSON.parse(readFileSync("/tmp/np-cache/natuurpunt_birdhide_12_2085_1368.geojson", "UTF8"))
 | 
					            const birdhides = JSON.parse(readFileSync(dir+"np-cache/natuurpunt_birdhide_12_2085_1368.geojson", "UTF8"))
 | 
				
			||||||
            expect(birdhides.features.length).deep.equal(5)
 | 
					            expect(birdhides.features.length).deep.equal(5)
 | 
				
			||||||
            expect(birdhides.features.some(f => f.properties.id === "node/5158056232"), "Didn't find birdhide node/5158056232 ").true
 | 
					            expect(birdhides.features.some(f => f.properties.id === "node/5158056232"), "Didn't find birdhide node/5158056232 ").true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue