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 | ||||
| .cache/* | ||||
| .idea/* | ||||
| .vscode/* | ||||
| scratch | ||||
| assets/editor-layer-index.json | ||||
| assets/generated/* | ||||
|  | @ -24,3 +23,16 @@ index_*.ts | |||
| .~lock.* | ||||
| *.doctest.ts | ||||
| 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 | ||||
| 
 | ||||
| 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 | ||||
| -------------------- | ||||
| 
 | ||||
|  |  | |||
|  | @ -39,6 +39,14 @@ export abstract class Conversion<TIn, TOut> { | |||
|         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>{ | ||||
|         return new Pipe( | ||||
|             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> { | ||||
|     /** | ||||
|      * The paths where this layer is originally saved. Triggers some extra checks | ||||
|      * @private | ||||
|      */ | ||||
|     private readonly _path?: string; | ||||
|     private readonly knownImagePaths: Set<string>; | ||||
|     private readonly _isBuiltin: boolean; | ||||
|     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"); | ||||
|         this.knownImagePaths = knownImagePaths; | ||||
|         this._validateImage = doesImageExist; | ||||
|         this._path = path; | ||||
|         this._isBuiltin = isBuiltin; | ||||
|         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.") | ||||
|             } | ||||
|             for (const image of images) { | ||||
|                 if (image.indexOf("{") >= 0) { | ||||
|                     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}`) | ||||
|                 } | ||||
|                 this._validateImage.convertJoin(image, context === undefined ? "" : ` in a layer defined in the theme ${context}`, errors, warnings, information) | ||||
|             } | ||||
| 
 | ||||
|             if (json.icon.endsWith(".svg")) { | ||||
|  | @ -150,9 +179,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> { | |||
|             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 + ")") | ||||
|             } | ||||
|             if (!this.knownImagePaths.has(theme.icon)) { | ||||
|                 errors.push("The theme image " + theme.icon + " is not attributed or not saved locally") | ||||
|             } | ||||
|             this._validateImage.convertJoin(theme.icon, context + ".icon", errors, warnings, information); | ||||
|             const dups = Utils.Dupiclates(json.layers.map(layer => layer["id"])) | ||||
|             if (dups.length > 0) { | ||||
|                 errors.push(`The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`) | ||||
|  | @ -166,16 +193,16 @@ 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
 | ||||
|                 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`) | ||||
|                 } | ||||
|                  | ||||
| 
 | ||||
|                 // Official, public themes must have a full english translation
 | ||||
|                 const checked = new ValidateLanguageCompleteness("en") | ||||
|                     .convert(theme, theme.id) | ||||
|                 errors.push(...checked.errors) | ||||
|                  | ||||
|                | ||||
| 
 | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|         } catch (e) { | ||||
|  | @ -192,10 +219,10 @@ class ValidateTheme extends DesugaringStep<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", | ||||
|             new ValidateTheme(knownImagePaths, path, isBuiltin, sharedTagRenderings), | ||||
|             new On("layers", new Each(new ValidateLayer(undefined, false, knownImagePaths))) | ||||
|             new ValidateTheme(doesImageExist, path, isBuiltin, sharedTagRenderings), | ||||
|             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++) { | ||||
|                 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.`) | ||||
|                 } else if (doesMatch) { | ||||
|                     // The current mapping is shadowed!
 | ||||
|  | @ -385,14 +412,15 @@ export class DetectShadowedMappings extends DesugaringStep<QuestionableTagRender | |||
| } | ||||
| 
 | ||||
| export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJson> { | ||||
|     private knownImagePaths: Set<string>; | ||||
|     constructor(knownImagePaths: Set<string>) { | ||||
|     private readonly _doesImageExist: DoesImageExist; | ||||
| 
 | ||||
|     constructor(doesImageExist: DoesImageExist) { | ||||
|         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": [ | ||||
|      *         { | ||||
|      *             "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
 | ||||
|      */ | ||||
|     convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[], information?: string[] } { | ||||
|         const errors = [] | ||||
|         const warnings = [] | ||||
|         const information = [] | ||||
|         const errors: string[] = [] | ||||
|         const warnings: string[] = [] | ||||
|         const information: string[] = [] | ||||
|         if (json.mappings === undefined || json.mappings.length === 0) { | ||||
|             return {result: json} | ||||
|         } | ||||
|  | @ -432,12 +460,10 @@ 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`) | ||||
| 
 | ||||
|                     for (const image of images) { | ||||
|                         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}`) | ||||
|                         } | ||||
|                         this._doesImageExist.convertJoin(image, ctx, errors, warnings, information); | ||||
| 
 | ||||
|                     } | ||||
|                      | ||||
| 
 | ||||
|                 } | ||||
|             } else if (ignore) { | ||||
|                 warnings.push(`${ctx}: unused '${ignoreToken}' - please remove this`) | ||||
|  | @ -454,10 +480,10 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ | |||
| } | ||||
| 
 | ||||
| export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> { | ||||
|     constructor(layerConfig?: LayerConfigJson, knownImagePaths?: Set<string>) { | ||||
|     constructor(layerConfig?: LayerConfigJson, doesImageExist?: DoesImageExist) { | ||||
|         super("Various validation on tagRenderingConfigs", | ||||
|             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 _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"); | ||||
|         this._path = path; | ||||
|         this._isBuiltin = isBuiltin; | ||||
|         this.knownImagePaths = knownImagePaths | ||||
|         this._doesImageExist = doesImageExist | ||||
|     } | ||||
| 
 | ||||
|     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) { | ||||
|                 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 ?? [])) | ||||
|                 errors.push(...(r.errors ?? [])) | ||||
|                 information.push(...(r.information ?? [])) | ||||
|  |  | |||
|  | @ -31,6 +31,7 @@ import {FixedUiElement} from "../../UI/Base/FixedUiElement"; | |||
| 
 | ||||
| export default class LayerConfig extends WithContextLoader { | ||||
| 
 | ||||
|     public static readonly syncSelectionAllowed = ["no", "local", "theme-only", "global"] as const; | ||||
|     public readonly id: string; | ||||
|     public readonly name: Translation; | ||||
|     public readonly description: Translation; | ||||
|  | @ -44,10 +45,8 @@ export default class LayerConfig extends WithContextLoader { | |||
|     public readonly maxzoom: number; | ||||
|     public readonly title?: TagRenderingConfig; | ||||
|     public readonly titleIcons: TagRenderingConfig[]; | ||||
| 
 | ||||
|     public readonly mapRendering: PointRenderingConfig[] | ||||
|     public readonly lineRendering: LineRenderingConfig[] | ||||
| 
 | ||||
|     public readonly units: Unit[]; | ||||
|     public readonly deletion: DeleteConfig | null; | ||||
|     public readonly allowMove: MoveConfig | null | ||||
|  | @ -57,15 +56,11 @@ export default class LayerConfig extends WithContextLoader { | |||
|      * In seconds | ||||
|      */ | ||||
|     public readonly maxAgeOfCache: number | ||||
| 
 | ||||
|     public readonly presets: PresetConfig[]; | ||||
| 
 | ||||
|     public readonly tagRenderings: TagRenderingConfig[]; | ||||
|     public readonly filters: FilterConfig[]; | ||||
|     public readonly filterIsSameAs: string; | ||||
|     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
 | ||||
| 
 | ||||
|     constructor( | ||||
|  | @ -74,18 +69,24 @@ export default class LayerConfig extends WithContextLoader { | |||
|         official: boolean = true | ||||
|     ) { | ||||
|         context = context + "." + json.id; | ||||
|         const translationContext = "layers:"+json.id | ||||
|         const translationContext = "layers:" + json.id | ||||
|         super(json, context) | ||||
|         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) { | ||||
|             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) { | ||||
|             throw "Layer " + this.id + " does not define a source section (" + context + ")" | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         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 + ")" | ||||
|         } | ||||
|  | @ -98,8 +99,8 @@ export default class LayerConfig extends WithContextLoader { | |||
|         } | ||||
| 
 | ||||
|         this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30 | ||||
|         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+"'" | ||||
|         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 + "'" | ||||
|         } | ||||
|         this.syncSelection = json.syncSelection ?? "no"; | ||||
|         const osmTags = TagUtils.Tag( | ||||
|  | @ -107,10 +108,10 @@ export default class LayerConfig extends WithContextLoader { | |||
|             context + "source.osmTags" | ||||
|         ); | ||||
| 
 | ||||
|         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, {}); | ||||
|         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, {}); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         if (json.source["geoJsonSource"] !== undefined) { | ||||
|             throw context + "Use 'geoJson' instead of 'geoJsonSource'"; | ||||
|         } | ||||
|  | @ -118,7 +119,7 @@ export default class LayerConfig extends WithContextLoader { | |||
|         if (json.source["geojson"] !== undefined) { | ||||
|             throw context + "Use 'geoJson' instead of 'geojson' (the J is a capital letter)"; | ||||
|         } | ||||
|          | ||||
| 
 | ||||
| 
 | ||||
|         this.source = new SourceConfig( | ||||
|             { | ||||
|  | @ -138,8 +139,8 @@ export default class LayerConfig extends WithContextLoader { | |||
| 
 | ||||
|         this.allowSplit = json.allowSplit ?? false; | ||||
|         this.name = Translations.T(json.name, translationContext + ".name"); | ||||
|         if(json.units!==undefined && !Array.isArray(json.units)){ | ||||
|             throw "At "+context+".units: the 'units'-section should be a list; you probably have an object there" | ||||
|         if (json.units !== undefined && !Array.isArray(json.units)) { | ||||
|             throw "At " + context + ".units: the 'units'-section should be a list; you probably have an object there" | ||||
|         } | ||||
|         this.units = (json.units ?? []).map(((unitJson, i) => Unit.fromJson(unitJson, `${context}.unit[${i}]`))) | ||||
| 
 | ||||
|  | @ -167,8 +168,8 @@ export default class LayerConfig extends WithContextLoader { | |||
|                 const index = kv.indexOf("="); | ||||
|                 let key = kv.substring(0, index).trim(); | ||||
|                 const r = "[a-z_][a-z0-9:]*" | ||||
|                 if(key.match(r) === null){ | ||||
|                     throw "At "+context+" invalid key for calculated tag: "+key+"; it should match "+r | ||||
|                 if (key.match(r) === null) { | ||||
|                     throw "At " + context + " invalid key for calculated tag: " + key + "; it should match " + r | ||||
|                 } | ||||
|                 const isStrict = key.endsWith(':') | ||||
|                 if (isStrict) { | ||||
|  | @ -343,14 +344,14 @@ export default class LayerConfig extends WithContextLoader { | |||
|     } | ||||
| 
 | ||||
|     public GenerateDocumentation(usedInThemes: string[], layerIsNeededBy?: Map<string, string[]>, dependencies: { | ||||
|         context?: string; | ||||
|         reason: string; | ||||
|         neededLayer: string; | ||||
|     }[] = [] | ||||
|                                  , addedByDefault = false, canBeIncluded = true): BaseUIElement { | ||||
|                                      context?: string; | ||||
|                                      reason: string; | ||||
|                                      neededLayer: string; | ||||
|                                  }[] = [] | ||||
|         , addedByDefault = false, canBeIncluded = true): BaseUIElement { | ||||
|         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 (addedByDefault) { | ||||
|  | @ -440,7 +441,7 @@ export default class LayerConfig extends WithContextLoader { | |||
|         let overpassLink: BaseUIElement = undefined; | ||||
|         if (Constants.priviliged_layers.indexOf(this.id) < 0) { | ||||
|             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) { | ||||
|                 console.error("Could not generate overpasslink for " + this.id) | ||||
|             } | ||||
|  |  | |||
|  | @ -352,7 +352,7 @@ export default class TagRenderingConfig { | |||
|             } | ||||
| 
 | ||||
|             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", | ||||
|                 "then": "./assets/themes/toilets/toilets.svg" | ||||
|               }, | ||||
|               { | ||||
|                 "if": "theme=transit", | ||||
|                 "then": "./assets/layers/transit_stops/bus_stop.svg" | ||||
|               }, | ||||
|               { | ||||
|                 "if": "theme=trees", | ||||
|                 "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": { | ||||
|                     "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)", | ||||
|  | @ -5232,6 +5237,39 @@ | |||
|             "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": { | ||||
|         "deletion": { | ||||
|             "extraDeleteReasons": { | ||||
|  | @ -5279,11 +5317,13 @@ | |||
|             } | ||||
|         }, | ||||
|         "tagRenderings": { | ||||
|             "2": { | ||||
|                 "override": { | ||||
|                     "question": "What kind of shop is this?" | ||||
|                 } | ||||
|             }, | ||||
|             "shops-name": { | ||||
|                 "question": "What is the name of this shop?" | ||||
|             }, | ||||
|             "shops-type-from-id": { | ||||
|                 "question": "What kind of shop is this?" | ||||
|             } | ||||
|         }, | ||||
|         "title": { | ||||
|  | @ -5962,6 +6002,169 @@ | |||
|             "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": { | ||||
|         "description": "A layer showing trees", | ||||
|         "name": "Tree", | ||||
|  |  | |||
|  | @ -1756,6 +1756,218 @@ | |||
|             "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": { | ||||
|         "tagRenderings": { | ||||
|             "slow_roads-surface": { | ||||
|  | @ -2259,6 +2471,158 @@ | |||
|             "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": { | ||||
|         "name": "pala eolica", | ||||
|         "presets": { | ||||
|  |  | |||
|  | @ -5183,11 +5183,13 @@ | |||
|             } | ||||
|         }, | ||||
|         "tagRenderings": { | ||||
|             "2": { | ||||
|                 "override": { | ||||
|                     "question": "Wat voor soort winkel is dit?" | ||||
|                 } | ||||
|             }, | ||||
|             "shops-name": { | ||||
|                 "question": "Wat is de naam van deze winkel?" | ||||
|             }, | ||||
|             "shops-type-from-id": { | ||||
|                 "question": "Wat voor soort winkel is dit?" | ||||
|             } | ||||
|         }, | ||||
|         "title": { | ||||
|  |  | |||
|  | @ -953,6 +953,10 @@ | |||
|         "description": "A map of public toilets", | ||||
|         "title": "Open Toilet Map" | ||||
|     }, | ||||
|     "transit": { | ||||
|         "description": "Plan your trip with the help of the public transport system.", | ||||
|         "title": "Bus routes" | ||||
|     }, | ||||
|     "trees": { | ||||
|         "description": "Map all the trees!", | ||||
|         "shortDescription": "Map all the trees", | ||||
|  |  | |||
|  | @ -577,6 +577,10 @@ | |||
|         "shortDescription": "Mappa tutti gli alberi", | ||||
|         "title": "Alberi" | ||||
|     }, | ||||
|     "waste": { | ||||
|         "description": "Mappa dei cestini per i rifiuti e i centri di raccolta e riciclo rifiuti.", | ||||
|         "title": "Rifiuti" | ||||
|     }, | ||||
|     "waste_basket": { | ||||
|         "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", | ||||
|  |  | |||
|  | @ -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: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:layeroverview": "ts-node scripts/generateLayerOverview.ts --no-fail", | ||||
|     "generate:layeroverview": "ts-node scripts/generateLayerOverview.ts", | ||||
|     "generate:licenses": "ts-node scripts/generateLicenseInfo.ts --no-fail", | ||||
|     "query:licenses": "ts-node scripts/generateLicenseInfo.ts --query", | ||||
|     "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: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'", | ||||
|     "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:charging-stations": "cd ./assets/layers/charging_station && ts-node csvToJson.ts && cd -", | ||||
|     "prepare-deploy": "npm run generate:service-worker && ./scripts/build.sh", | ||||
|  |  | |||
|  | @ -45,13 +45,103 @@ 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); | ||||
|         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) => { | ||||
|             try { | ||||
|                 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 | ||||
| 
 | ||||
| # 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: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 generate:layouts  | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,10 +1,11 @@ | |||
| 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 {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson"; | ||||
| import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; | ||||
| import Constants from "../Models/Constants"; | ||||
| import { | ||||
|     DoesImageExist, | ||||
|     PrevalidateTheme, | ||||
|     ValidateLayer, | ||||
|     ValidateTagRenderings, | ||||
|  | @ -25,6 +26,51 @@ import {Utils} from "../Utils"; | |||
| 
 | ||||
| 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 })[] }[]) { | ||||
|         const perId = new Map<string, any>(); | ||||
|         for (const theme of themes) { | ||||
|  | @ -69,23 +115,23 @@ class LayerOverviewUtils { | |||
|     } | ||||
| 
 | ||||
|     writeTheme(theme: LayoutConfigJson) { | ||||
|         if (!existsSync("./assets/generated/themes")) { | ||||
|             mkdirSync("./assets/generated/themes"); | ||||
|         if (!existsSync(LayerOverviewUtils.themePath)) { | ||||
|             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) { | ||||
|         if (!existsSync("./assets/generated/layers")) { | ||||
|             mkdirSync("./assets/generated/layers"); | ||||
|         if (!existsSync(LayerOverviewUtils.layerPath)) { | ||||
|             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 validator = new ValidateTagRenderings(undefined, knownImagePaths); | ||||
| 
 | ||||
|         const validator = new ValidateTagRenderings(undefined, doesImageExist); | ||||
|         for (const key in questions["default"]) { | ||||
|             if (key === "id") { | ||||
|                 continue | ||||
|  | @ -93,7 +139,7 @@ class LayerOverviewUtils { | |||
|             questions[key].id = key; | ||||
|             questions[key]["source"] = "shared-questions" | ||||
|             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) | ||||
|         } | ||||
|         for (const key in icons["default"]) { | ||||
|  | @ -104,9 +150,9 @@ class LayerOverviewUtils { | |||
|                 continue | ||||
|             } | ||||
|             icons[key].id = key; | ||||
|             const config =  <TagRenderingConfigJson>icons[key] | ||||
|             validator.convertStrict(config, "generate-layer-overview:tagRenderings/icons.json:"+key) | ||||
|             dict.set(key,config) | ||||
|             const config = <TagRenderingConfigJson>icons[key] | ||||
|             validator.convertStrict(config, "generate-layer-overview:tagRenderings/icons.json:" + key) | ||||
|             dict.set(key, config) | ||||
|         } | ||||
| 
 | ||||
|         dict.forEach((value, key) => { | ||||
|  | @ -149,16 +195,18 @@ class LayerOverviewUtils { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     main(_: string[]) { | ||||
|     main(args: string[]) { | ||||
|          | ||||
|         const forceReload = args.some(a => a == "--force") | ||||
| 
 | ||||
|         const licensePaths = new Set<string>() | ||||
|         for (const i in licenses) { | ||||
|             licensePaths.add(licenses[i].path) | ||||
|         } | ||||
| 
 | ||||
|         const sharedLayers = this.buildLayerIndex(licensePaths); | ||||
|         const sharedThemes = this.buildThemeIndex(licensePaths, sharedLayers) | ||||
|         const doesImageExist = new DoesImageExist(licensePaths, existsSync) | ||||
|         const sharedLayers = this.buildLayerIndex(doesImageExist, forceReload); | ||||
|         const recompiledThemes : string[] = [] | ||||
|         const sharedThemes = this.buildThemeIndex(doesImageExist, sharedLayers, recompiledThemes, forceReload) | ||||
| 
 | ||||
|         writeFileSync("./assets/generated/known_layers_and_themes.json", JSON.stringify({ | ||||
|             "layers": Array.from(sharedLayers.values()), | ||||
|  | @ -168,7 +216,7 @@ class LayerOverviewUtils { | |||
|         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
 | ||||
|             const iconsPerTheme = | ||||
|                 Array.from(sharedThemes.values()).map(th => ({ | ||||
|  | @ -188,28 +236,42 @@ class LayerOverviewUtils { | |||
|         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
 | ||||
|         // At the same time, an index of available layers is built.
 | ||||
|         console.log("   ---------- VALIDATING BUILTIN LAYERS ---------") | ||||
| 
 | ||||
|         const sharedTagRenderings = this.getSharedTagRenderings(knownImagePaths); | ||||
|         const layerFiles = ScriptUtils.getLayerFiles(); | ||||
|         const sharedTagRenderings = this.getSharedTagRenderings(doesImageExist); | ||||
|         const sharedLayers = new Map<string, LayerConfigJson>() | ||||
|         const state: DesugaringContext = { | ||||
|             tagRenderings: sharedTagRenderings, | ||||
|             sharedLayers | ||||
|         } | ||||
|         const prepLayer = new PrepareLayer(state); | ||||
|         for (const sharedLayerJson of layerFiles) { | ||||
|             const context = "While building builtin layer " + sharedLayerJson.path | ||||
|             const fixed = prepLayer.convertStrict(sharedLayerJson.parsed, context) | ||||
|         const skippedLayers: string[] = [] | ||||
|         const recompiledLayers: string[] = [] | ||||
|         for (const sharedLayerPath of ScriptUtils.getLayerPaths()) { | ||||
| 
 | ||||
|             { | ||||
|                 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; | ||||
|                 } | ||||
|              | ||||
|             if(fixed.source.osmTags["and"] === undefined){ | ||||
|             } | ||||
| 
 | ||||
|             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]} | ||||
|             } | ||||
|              | ||||
|             const validator = new ValidateLayer(sharedLayerJson.path, true, knownImagePaths); | ||||
| 
 | ||||
|             const validator = new ValidateLayer(sharedLayerPath, true, doesImageExist); | ||||
|             validator.convertStrict(fixed, context) | ||||
| 
 | ||||
|             if (sharedLayers.has(fixed.id)) { | ||||
|  | @ -217,39 +279,18 @@ class LayerOverviewUtils { | |||
|             } | ||||
| 
 | ||||
|             sharedLayers.set(fixed.id, fixed) | ||||
|             recompiledLayers.push(fixed.id) | ||||
| 
 | ||||
|             this.writeLayer(fixed) | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         console.log("Recompiled layers " + recompiledLayers.join(", ") + " and skipped " + skippedLayers.length + " layers") | ||||
| 
 | ||||
|         return sharedLayers; | ||||
|     } | ||||
| 
 | ||||
|     private static publicLayerIdsFrom(themefiles: LayoutConfigJson[]): Set<string> { | ||||
|         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> { | ||||
|     private buildThemeIndex(doesImageExist: DoesImageExist, sharedLayers: Map<string, LayerConfigJson>, recompiledThemes: string[], forceReload: boolean): Map<string, LayoutConfigJson> { | ||||
|         console.log("   ---------- VALIDATING BUILTIN THEMES ---------") | ||||
|         const themeFiles = ScriptUtils.getThemeFiles(); | ||||
|         const fixed = new Map<string, LayoutConfigJson>(); | ||||
|  | @ -258,23 +299,33 @@ class LayerOverviewUtils { | |||
| 
 | ||||
|         const convertState: DesugaringContext = { | ||||
|             sharedLayers, | ||||
|             tagRenderings: this.getSharedTagRenderings(knownImagePaths), | ||||
|             tagRenderings: this.getSharedTagRenderings(doesImageExist), | ||||
|             publicLayers | ||||
|         } | ||||
|         const nonDefaultLanguages : {theme: string, language: string}[] = [] | ||||
|         const skippedThemes: string[] = [] | ||||
|         for (const themeInfo of themeFiles) { | ||||
| 
 | ||||
|             const themePath = themeInfo.path; | ||||
|             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) | ||||
|             try { | ||||
| 
 | ||||
|                 themeFile = new PrepareTheme(convertState).convertStrict(themeFile, themePath) | ||||
| 
 | ||||
|                 if (knownImagePaths === undefined) { | ||||
|                     throw "Could not load known images/licenses" | ||||
|                 } | ||||
|                 new ValidateThemeAndLayers(knownImagePaths, themePath, true, convertState.tagRenderings) | ||||
|                 new ValidateThemeAndLayers(doesImageExist, themePath, true, convertState.tagRenderings) | ||||
|                     .convertStrict(themeFile, themePath) | ||||
| 
 | ||||
|                 this.writeTheme(themeFile) | ||||
|  | @ -293,6 +344,9 @@ class LayerOverviewUtils { | |||
|                 mustHaveLanguage: t.mustHaveLanguage?.length > 0, | ||||
|             } | ||||
|         })); | ||||
| 
 | ||||
|         console.log("Recompiled themes " + recompiledThemes.join(", ") + " and skipped " + skippedThemes.length + " themes") | ||||
| 
 | ||||
|         return fixed; | ||||
| 
 | ||||
|     } | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"; | |||
| import TagRenderingQuestion from "../../../UI/Popup/TagRenderingQuestion"; | ||||
| import {UIEventSource} from "../../../Logic/UIEventSource"; | ||||
| import { expect } from 'chai'; | ||||
| import Locale from "../../../UI/i18n/Locale"; | ||||
| 
 | ||||
| describe("TagRenderingQuestion", () => { | ||||
| 
 | ||||
|  | @ -27,6 +28,7 @@ describe("TagRenderingQuestion", () => { | |||
| 
 | ||||
|     it("should have a freeform text field with a type explanation", () => { | ||||
| 
 | ||||
|         Locale.language.setData("en") | ||||
|         const configJson = <TagRenderingConfigJson>{ | ||||
|             id: "test-tag-rendering", | ||||
|             question: "Question?", | ||||
|  |  | |||
|  | @ -28,23 +28,28 @@ function initDownloads(query: string){ | |||
| describe("GenerateCache", () => { | ||||
|      | ||||
|         it("should generate a cached file for the Natuurpunt-theme", async () => { | ||||
|             if (existsSync("/tmp/np-cache")) { | ||||
|                 ScriptUtils.readDirRecSync("/tmp/np-cache").forEach(p => unlinkSync(p)) | ||||
|                 rmdirSync("/tmp/np-cache") | ||||
|             // We use /var/tmp instead of /tmp, as more OS's (such as MAC) have this
 | ||||
|             const dir = "/var/tmp/" | ||||
|             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( | ||||
|                 "(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([ | ||||
|                 "natuurpunt", | ||||
|                 "12", | ||||
|                 "/tmp/np-cache", | ||||
|                 dir+"np-cache", | ||||
|                 "51.15423567022531", "3.250579833984375", "51.162821593316934", "3.262810707092285", | ||||
|                 "--generate-point-overview", "nature_reserve,visitor_information_centre" | ||||
|             ]) | ||||
|             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.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