forked from MapComplete/MapComplete
		
	Feature: layer validation system now builds a dependency graph and only updates what is needed, makes "refresh:layeroverview" redundant
This commit is contained in:
		
							parent
							
								
									1bd060df82
								
							
						
					
					
						commit
						fda0bc6b2e
					
				
					 19 changed files with 301 additions and 186 deletions
				
			
		|  | @ -141,11 +141,12 @@ | ||||||
|   "lineRendering": [], |   "lineRendering": [], | ||||||
|   "tagRenderings": [ |   "tagRenderings": [ | ||||||
|     { |     { | ||||||
|  |       "id": "images", | ||||||
|       "render": { |       "render": { | ||||||
|         "special": { |         "special": { | ||||||
|           "before": "{image_carousel(toilets:panoramax;toilets:mapillary;toilets:images)}", |           "before": "{image_carousel(toilets:panoramax;toilets:mapillary;toilets:images)}", | ||||||
|           "type": "image_upload", |           "type": "image_upload", | ||||||
|           "key": "toilets:panoramax", |           "image_key": "toilets:panoramax", | ||||||
|           "label": { |           "label": { | ||||||
|             "en": "Add a picture of the toilets", |             "en": "Add a picture of the toilets", | ||||||
|             "nl": "Voeg een foto van de toiletten toe" |             "nl": "Voeg een foto van de toiletten toe" | ||||||
|  | @ -153,19 +154,6 @@ | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     { |  | ||||||
|       "id": "dead", |  | ||||||
|       "labels": [ |  | ||||||
|         "hidden", |  | ||||||
|         "relevant_questions" |  | ||||||
|       ], |  | ||||||
|       "condition": { |  | ||||||
|         "and": [ |  | ||||||
|           "id=" |  | ||||||
|         ] |  | ||||||
|       }, |  | ||||||
|       "render": "Only used to make sure 'relevant-questions' is known" |  | ||||||
|     }, |  | ||||||
|     { |     { | ||||||
|       "builtin": "toilet.amenity-no-prefix", |       "builtin": "toilet.amenity-no-prefix", | ||||||
|       "prefix": "toilets", |       "prefix": "toilets", | ||||||
|  |  | ||||||
|  | @ -18,6 +18,11 @@ | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "id": "grouptitle", |       "id": "grouptitle", | ||||||
|  |       "labels": [ | ||||||
|  |         "all", | ||||||
|  |         "hidden" | ||||||
|  |       ], | ||||||
|  |       "icon": "./assets/layers/toilet/toilets.svg", | ||||||
|       "render": { |       "render": { | ||||||
|         "en": "Toilet information", |         "en": "Toilet information", | ||||||
|         "nl": "Informatie over de toiletten" |         "nl": "Informatie over de toiletten" | ||||||
|  | @ -61,7 +66,8 @@ | ||||||
|       "id": "toilet-question-box", |       "id": "toilet-question-box", | ||||||
|       "labels": [ |       "labels": [ | ||||||
|         "toilet-questions", |         "toilet-questions", | ||||||
|         "all" |         "all", | ||||||
|  |         "hidden" | ||||||
|       ], |       ], | ||||||
|       "render": { |       "render": { | ||||||
|         "special": { |         "special": { | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								package.json
									
										
									
									
									
								
							|  | @ -98,16 +98,15 @@ | ||||||
|     "reset:translations": "vite-node scripts/generateTranslations.ts -- --ignore-weblate", |     "reset:translations": "vite-node scripts/generateTranslations.ts -- --ignore-weblate", | ||||||
|     "generate:layouts": "export NODE_OPTIONS=\"--max-old-space-size=8192\" &&  vite-node scripts/generateLayouts.ts", |     "generate:layouts": "export NODE_OPTIONS=\"--max-old-space-size=8192\" &&  vite-node scripts/generateLayouts.ts", | ||||||
|     "generate:docs": "rm -rf Docs/Themes/* && rm -rf Docs/Layers/* && rm -rf Docs/TagInfo && mkdir Docs/TagInfo && export NODE_OPTIONS=\"--max-old-space-size=16000\" && vite-node scripts/generateDocs.ts && vite-node scripts/generateTaginfoProjectFiles.ts", |     "generate:docs": "rm -rf Docs/Themes/* && rm -rf Docs/Layers/* && rm -rf Docs/TagInfo && mkdir Docs/TagInfo && export NODE_OPTIONS=\"--max-old-space-size=16000\" && vite-node scripts/generateDocs.ts && vite-node scripts/generateTaginfoProjectFiles.ts", | ||||||
|     "generate:layeroverview": "export NODE_OPTIONS=\"--max-old-space-size=16000\" && vite-node scripts/generateLayerOverview.ts", |  | ||||||
|     "generate:mapcomplete-changes-theme": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts -- --generate-change-map", |     "generate:mapcomplete-changes-theme": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts -- --generate-change-map", | ||||||
|     "refresh:layeroverview": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts -- --force", |  | ||||||
|     "generate:licenses": "vite-node scripts/generateLicenseInfo.ts -- --no-fail", |     "generate:licenses": "vite-node scripts/generateLicenseInfo.ts -- --no-fail", | ||||||
|  |     "generate:layeroverview": "export NODE_OPTIONS=\"--max-old-space-size=16000\" && vite-node scripts/generateLayerOverview.ts", | ||||||
|  |     "prep:layeroverview": "./scripts/initFiles.sh", | ||||||
|  |     "reset:layeroverview": "npm run prep:layeroverview && npm run generate:layeroverview", | ||||||
|     "query:licenses": "vite-node scripts/generateLicenseInfo.ts -- --query && npm run generate:licenses", |     "query:licenses": "vite-node scripts/generateLicenseInfo.ts -- --query && npm run generate:licenses", | ||||||
|     "clean:licenses": "find . -type f -name \"*.license\" -exec rm -f {} +", |     "clean:licenses": "find . -type f -name \"*.license\" -exec rm -f {} +", | ||||||
|     "generate:contributor-list": "vite-node scripts/generateContributors.ts", |     "generate:contributor-list": "vite-node scripts/generateContributors.ts", | ||||||
|     "generate:service-worker": "tsc src/service-worker.ts --outFile public/service-worker.js && git_hash=$(git rev-parse HEAD) && sed -i.bak \"s/GITHUB-COMMIT/$git_hash/\" public/service-worker.js && rm public/service-worker.js.bak", |     "generate:service-worker": "tsc src/service-worker.ts --outFile public/service-worker.js && git_hash=$(git rev-parse HEAD) && sed -i.bak \"s/GITHUB-COMMIT/$git_hash/\" public/service-worker.js && rm public/service-worker.js.bak", | ||||||
|     "reset:layeroverview": "npm run prep:layeroverview && npm run generate:layeroverview && npm run refresh:layeroverview", |  | ||||||
|     "prep:layeroverview": "./scripts/initFiles.sh", |  | ||||||
|     "generate": "npm run generate:licenses && npm run generate:images && npm run generate:charging-stations && npm run generate:translations && npm run refresh:layeroverview && npm run generate:service-worker", |     "generate": "npm run generate:licenses && npm run generate:images && npm run generate:charging-stations && npm run generate:translations && npm run refresh:layeroverview && npm run generate:service-worker", | ||||||
|     "generate:charging-stations": "cd ./assets/layers/charging_station && vite-node csvToJson.ts && cd -", |     "generate:charging-stations": "cd ./assets/layers/charging_station && vite-node csvToJson.ts && cd -", | ||||||
|     "clean:tests": "find . -type f -name \"*.doctest.ts\" | xargs -r rm", |     "clean:tests": "find . -type f -name \"*.doctest.ts\" | xargs -r rm", | ||||||
|  | @ -224,8 +223,7 @@ | ||||||
|     "latlon2country": "^1.2.7", |     "latlon2country": "^1.2.7", | ||||||
|     "libphonenumber-js": "^1.11.19", |     "libphonenumber-js": "^1.11.19", | ||||||
|     "mangrove-reviews-typescript": "^1.3.1", |     "mangrove-reviews-typescript": "^1.3.1", | ||||||
|     "maplibre": "^0.0.1-security", |     "maplibre-gl": "^5.1.0", | ||||||
|     "maplibre-gl": "^5.1.0  ", |  | ||||||
|     "marked": "^12.0.2", |     "marked": "^12.0.2", | ||||||
|     "monaco-editor": "^0.46.0", |     "monaco-editor": "^0.46.0", | ||||||
|     "mvt-to-geojson": "^0.0.6", |     "mvt-to-geojson": "^0.0.6", | ||||||
|  |  | ||||||
|  | @ -6,13 +6,14 @@ import { AllKnownLayoutsLazy } from "../src/Customizations/AllKnownLayouts" | ||||||
| import { Utils } from "../src/Utils" | import { Utils } from "../src/Utils" | ||||||
| import { | import { | ||||||
|     MappingConfigJson, |     MappingConfigJson, | ||||||
|     QuestionableTagRenderingConfigJson, |     QuestionableTagRenderingConfigJson | ||||||
| } from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" | } from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" | ||||||
| import { TagConfigJson } from "../src/Models/ThemeConfig/Json/TagConfigJson" | import { TagConfigJson } from "../src/Models/ThemeConfig/Json/TagConfigJson" | ||||||
| import { TagUtils } from "../src/Logic/Tags/TagUtils" | import { TagUtils } from "../src/Logic/Tags/TagUtils" | ||||||
| import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson" | import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson" | ||||||
| import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable" | import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable" | ||||||
| import * as questions from "../assets/layers/questions/questions.json" | import * as questions from "../assets/layers/questions/questions.json" | ||||||
|  | 
 | ||||||
| export class GenerateFavouritesLayer extends Script { | export class GenerateFavouritesLayer extends Script { | ||||||
|     private readonly layers: LayerConfigJson[] = [] |     private readonly layers: LayerConfigJson[] = [] | ||||||
| 
 | 
 | ||||||
|  | @ -202,7 +203,7 @@ export class GenerateFavouritesLayer extends Script { | ||||||
|             string, |             string, | ||||||
|             TagRenderingConfigJson[] |             TagRenderingConfigJson[] | ||||||
|         >() |         >() | ||||||
|         const path = "./src/assets/generated/layers/icons.json" |         const path = "./public/assets/generated/layers/icons.json" | ||||||
|         if (existsSync(path)) { |         if (existsSync(path)) { | ||||||
|             const config = <LayerConfigJson>JSON.parse(readFileSync(path, "utf8")) |             const config = <LayerConfigJson>JSON.parse(readFileSync(path, "utf8")) | ||||||
|             for (const tagRendering of config.tagRenderings) { |             for (const tagRendering of config.tagRenderings) { | ||||||
|  |  | ||||||
|  | @ -9,16 +9,12 @@ import { | ||||||
|     DoesImageExist, |     DoesImageExist, | ||||||
|     PrevalidateTheme, |     PrevalidateTheme, | ||||||
|     ValidateLayer, |     ValidateLayer, | ||||||
|     ValidateThemeEnsemble, |     ValidateThemeEnsemble | ||||||
| } from "../src/Models/ThemeConfig/Conversion/Validation" | } from "../src/Models/ThemeConfig/Conversion/Validation" | ||||||
| import { Translation } from "../src/UI/i18n/Translation" | import { Translation } from "../src/UI/i18n/Translation" | ||||||
| import { PrepareLayer } from "../src/Models/ThemeConfig/Conversion/PrepareLayer" | import { PrepareLayer } from "../src/Models/ThemeConfig/Conversion/PrepareLayer" | ||||||
| import { PrepareTheme } from "../src/Models/ThemeConfig/Conversion/PrepareTheme" | import { PrepareTheme } from "../src/Models/ThemeConfig/Conversion/PrepareTheme" | ||||||
| import { | import { Conversion, DesugaringContext, DesugaringStep } from "../src/Models/ThemeConfig/Conversion/Conversion" | ||||||
|     Conversion, |  | ||||||
|     DesugaringContext, |  | ||||||
|     DesugaringStep, |  | ||||||
| } from "../src/Models/ThemeConfig/Conversion/Conversion" |  | ||||||
| import { Utils } from "../src/Utils" | import { Utils } from "../src/Utils" | ||||||
| import Script from "./Script" | import Script from "./Script" | ||||||
| import { AllSharedLayers } from "../src/Customizations/AllSharedLayers" | import { AllSharedLayers } from "../src/Customizations/AllSharedLayers" | ||||||
|  | @ -35,6 +31,7 @@ import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable" | ||||||
| import { ValidateThemeAndLayers } from "../src/Models/ThemeConfig/Conversion/ValidateThemeAndLayers" | import { ValidateThemeAndLayers } from "../src/Models/ThemeConfig/Conversion/ValidateThemeAndLayers" | ||||||
| import { ExtractImages } from "../src/Models/ThemeConfig/Conversion/FixImages" | import { ExtractImages } from "../src/Models/ThemeConfig/Conversion/FixImages" | ||||||
| import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson" | import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson" | ||||||
|  | import { LayerConfigDependencyGraph, LevelInfo } from "../src/Models/ThemeConfig/LayerConfigDependencyGraph" | ||||||
| 
 | 
 | ||||||
| // This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files.
 | // This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files.
 | ||||||
| // It spits out an overview of those to be used to load them
 | // It spits out an overview of those to be used to load them
 | ||||||
|  | @ -138,8 +135,171 @@ class AddIconSummary extends DesugaringStep<{ raw: LayerConfigJson; parsed: Laye | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | class LayerBuilder extends Conversion<object, Map<string, LayerConfigJson>> { | ||||||
|  |     private readonly _dependencies: ReadonlyMap<string, string[]> | ||||||
|  |     private readonly _states: Map<string, "clean" | "dirty" | "changed"> | ||||||
|  |     private readonly prepareLayer: PrepareLayer | ||||||
|  |     private readonly _levels: LevelInfo[] | ||||||
|  |     private readonly _loadedIds: Set<string> = new Set<string>() | ||||||
|  |     private readonly _layerConfigJsons = new Map<string, LayerConfigJson> | ||||||
|  |     private readonly _desugaringState: DesugaringContext | ||||||
|  | 
 | ||||||
|  |     constructor( | ||||||
|  |         layerConfigJsons: LayerConfigJson[], | ||||||
|  |         dependencies: Map<string, string[]>, | ||||||
|  |         levels: LevelInfo[], | ||||||
|  |         states: Map<string, "clean" | "dirty" | "changed">, | ||||||
|  |         sharedTagRenderings: QuestionableTagRenderingConfigJson[]) { | ||||||
|  |         super("Builds all the layers, writes them to file", [], "LayerBuilder") | ||||||
|  |         this._levels = levels | ||||||
|  |         this._dependencies = dependencies | ||||||
|  |         this._states = states | ||||||
|  |         this._desugaringState = { | ||||||
|  |             tagRenderings: LayerOverviewUtils.asDict(sharedTagRenderings), | ||||||
|  |             tagRenderingOrder: sharedTagRenderings.map((tr) => tr.id), | ||||||
|  |             sharedLayers: AllSharedLayers.getSharedLayersConfigs() | ||||||
|  |         } | ||||||
|  |         this.prepareLayer = new PrepareLayer(this._desugaringState) | ||||||
|  |         for (const layerConfigJson of layerConfigJsons) { | ||||||
|  |             this._layerConfigJsons.set(layerConfigJson.id, layerConfigJson) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static targetPath(id: string): string { | ||||||
|  |         return `${LayerOverviewUtils.layerPath}${id}.json` | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static sourcePath(id: string): string { | ||||||
|  |         return `./assets/layers/${id}/${id}.json` | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     writeLayer(layer: LayerConfigJson) { | ||||||
|  |         if (!existsSync(LayerOverviewUtils.layerPath)) { | ||||||
|  |             mkdirSync(LayerOverviewUtils.layerPath) | ||||||
|  |         } | ||||||
|  |         writeFileSync( | ||||||
|  |             LayerBuilder.targetPath(layer.id), | ||||||
|  |             JSON.stringify(layer, null, "  "), | ||||||
|  |             { encoding: "utf8" } | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     public buildLayer(id: string, context: ConversionContext, isLooping: boolean = false): LayerConfigJson { | ||||||
|  |         if (id === "questions") { | ||||||
|  |             return undefined | ||||||
|  |         } | ||||||
|  |         const deps = this._dependencies.get(id) | ||||||
|  |         if (!isLooping) { | ||||||
|  |             // Beware of the looping traps. Bring the leaf to the statue to teleport to "The Lab" (<ref>submachine 4</ref>)
 | ||||||
|  |             const unbuilt = deps.filter(depId => !this._loadedIds.has(depId)) | ||||||
|  |             for (const unbuiltId of unbuilt) { | ||||||
|  |                 this.buildLayer(unbuiltId, context) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         context = context.inOperation("building Layer " + id).enters("layer", id) | ||||||
|  | 
 | ||||||
|  |         const config = this._layerConfigJsons.get(id) | ||||||
|  |         const prepped = this.prepareLayer.convert(config, context) | ||||||
|  |         this._loadedIds.add(id) | ||||||
|  |         this._desugaringState.sharedLayers.set(id, prepped) | ||||||
|  |         return prepped | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private buildLooping(ids: string[], context: ConversionContext) { | ||||||
|  |         const origIds: ReadonlyArray<string> = [...ids] | ||||||
|  | 
 | ||||||
|  |         const deps = this._dependencies | ||||||
|  |         const allDeps = Utils.Dedup([].concat(...ids.map(id => deps.get(id)))) | ||||||
|  |         const depsRecord = Utils.asRecord(Array.from(deps.keys()), k => | ||||||
|  |             deps.get(k).filter(dep => ids.indexOf(dep) >= 0)) | ||||||
|  |         const revDeps = Utils.TransposeMap(depsRecord) | ||||||
|  |         for (const someDep of allDeps) { | ||||||
|  |             if (ids.indexOf(someDep) >= 0) { | ||||||
|  |                 // BY definition, we _will_ need this dependency
 | ||||||
|  |                 // We add a small stub
 | ||||||
|  |                 this._desugaringState.sharedLayers.set(someDep, { | ||||||
|  |                     id: someDep, | ||||||
|  |                     pointRendering: [], | ||||||
|  |                     tagRenderings: [], | ||||||
|  |                     filter: [], | ||||||
|  |                     source: "special:stub", | ||||||
|  |                     allowMove: true | ||||||
|  |                 }) | ||||||
|  |                 continue | ||||||
|  |             } | ||||||
|  |             // Make sure all are direct dependencies are loaded
 | ||||||
|  |             if (!this._loadedIds.has(someDep)) { | ||||||
|  |                 this.buildLayer(someDep, context) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         while (ids.length > 0) { | ||||||
|  |             const first = ids.pop() | ||||||
|  |             if (first === "questions") { | ||||||
|  |                 continue | ||||||
|  |             } | ||||||
|  |             const oldConfig = this._desugaringState.sharedLayers.get(first) ?? this._layerConfigJsons.get(first) | ||||||
|  |             const newConfig = this.buildLayer(first, context.inOperation("resolving a looped dependency"), true) | ||||||
|  |             const isDifferent = JSON.stringify(oldConfig) !== JSON.stringify(newConfig) | ||||||
|  | 
 | ||||||
|  |             if (isDifferent) { | ||||||
|  |                 const toRunAgain = revDeps[first] ?? [] | ||||||
|  |                 for (const id of toRunAgain) { | ||||||
|  |                     if (ids.indexOf(id) < 0) { | ||||||
|  |                         ids.push(id) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         for (const id of origIds) { | ||||||
|  |             this.writeLayer(this._desugaringState.sharedLayers.get(id)) | ||||||
|  |         } | ||||||
|  |         console.log("Done with the looping layers!") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public convert(o, context: ConversionContext): | ||||||
|  |         Map<string, LayerConfigJson> { | ||||||
|  | 
 | ||||||
|  |         for (const level of this._levels | ||||||
|  |             ) { | ||||||
|  |             if (level.loop) { | ||||||
|  |                 this.buildLooping(level.ids, context) | ||||||
|  |                 continue | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             for (let i = 0; i < level.ids.length; i++) { | ||||||
|  |                 const id = level.ids[i] | ||||||
|  |                 ScriptUtils.erasableLog(`Building level ${level.level}: validating layer ${i + 1}/${level.ids.length}: ${id}`) | ||||||
|  |                 if (id === "questions") { | ||||||
|  |                     continue | ||||||
|  |                 } | ||||||
|  |                 if (this._states.get(id) === "clean") { | ||||||
|  |                     const file = readFileSync(LayerBuilder.targetPath(id), "utf-8") | ||||||
|  |                     if (file.length > 3) { | ||||||
|  |                         try { | ||||||
|  |                             const loaded = JSON.parse(file) | ||||||
|  |                             this._desugaringState.sharedLayers.set(id, loaded) | ||||||
|  |                             continue | ||||||
|  |                         } catch (e) { | ||||||
|  |                             console.error("Could not load generated layer file for ", id, " building it instead") | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 const prepped = this.buildLayer(id, context) | ||||||
|  |                 this.writeLayer(prepped) | ||||||
|  |                 LayerOverviewUtils.extractJavascriptCodeForLayer(prepped) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         context.info("Recompiled " + this._loadedIds.size + " layers") | ||||||
|  |         return this._desugaringState.sharedLayers | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| class LayerOverviewUtils extends Script { | class LayerOverviewUtils extends Script { | ||||||
|     public static readonly layerPath = "./src/assets/generated/layers/" |     public static readonly layerPath = "./public/assets/generated/layers/" | ||||||
|     public static readonly themePath = "./public/assets/generated/themes/" |     public static readonly themePath = "./public/assets/generated/themes/" | ||||||
| 
 | 
 | ||||||
|     constructor() { |     constructor() { | ||||||
|  | @ -190,7 +350,7 @@ class LayerOverviewUtils extends Script { | ||||||
|         return Translations.T(t).OnEveryLanguage((s) => parse_html(s).textContent).translations |         return Translations.T(t).OnEveryLanguage((s) => parse_html(s).textContent).translations | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     shouldBeUpdated(sourcefile: string | string[], targetfile: string): boolean { |     public static shouldBeUpdated(sourcefile: string | string[], targetfile: string): boolean { | ||||||
|         if (!existsSync(targetfile)) { |         if (!existsSync(targetfile)) { | ||||||
|             return true |             return true | ||||||
|         } |         } | ||||||
|  | @ -359,17 +519,6 @@ class LayerOverviewUtils extends Script { | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     writeLayer(layer: LayerConfigJson) { |  | ||||||
|         if (!existsSync(LayerOverviewUtils.layerPath)) { |  | ||||||
|             mkdirSync(LayerOverviewUtils.layerPath) |  | ||||||
|         } |  | ||||||
|         writeFileSync( |  | ||||||
|             `${LayerOverviewUtils.layerPath}${layer.id}.json`, |  | ||||||
|             JSON.stringify(layer, null, "  "), |  | ||||||
|             { encoding: "utf8" } |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     static asDict( |     static asDict( | ||||||
|         trs: QuestionableTagRenderingConfigJson[] |         trs: QuestionableTagRenderingConfigJson[] | ||||||
|     ): Map<string, QuestionableTagRenderingConfigJson> { |     ): Map<string, QuestionableTagRenderingConfigJson> { | ||||||
|  | @ -481,13 +630,6 @@ class LayerOverviewUtils extends Script { | ||||||
|                 ?.split(",") ?? [] |                 ?.split(",") ?? [] | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         const layerWhitelist = new Set( |  | ||||||
|             args |  | ||||||
|                 .find((a) => a.startsWith("--layers=")) |  | ||||||
|                 ?.substring("--layers=".length) |  | ||||||
|                 ?.split(",") ?? [] |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|         const forceReload = args.some((a) => a == "--force") |         const forceReload = args.some((a) => a == "--force") | ||||||
| 
 | 
 | ||||||
|         const licensePaths = new Set<string>() |         const licensePaths = new Set<string>() | ||||||
|  | @ -495,7 +637,7 @@ class LayerOverviewUtils extends Script { | ||||||
|             licensePaths.add(licenses[i].path) |             licensePaths.add(licenses[i].path) | ||||||
|         } |         } | ||||||
|         const doesImageExist = new DoesImageExist(licensePaths, existsSync) |         const doesImageExist = new DoesImageExist(licensePaths, existsSync) | ||||||
|         const sharedLayers = this.buildLayerIndex(doesImageExist, forceReload, layerWhitelist) |         const sharedLayers = this.buildLayerIndex(doesImageExist) | ||||||
| 
 | 
 | ||||||
|         const priviliged = new Set<string>(Constants.priviliged_layers) |         const priviliged = new Set<string>(Constants.priviliged_layers) | ||||||
|         sharedLayers.forEach((_, key) => { |         sharedLayers.forEach((_, key) => { | ||||||
|  | @ -582,9 +724,6 @@ class LayerOverviewUtils extends Script { | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (AllSharedLayers.getSharedLayersConfigs().size == 0) { |  | ||||||
|             console.error("This was a bootstrapping-run. Run generate layeroverview again!") |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private parseLayer( |     private parseLayer( | ||||||
|  | @ -606,81 +745,89 @@ class LayerOverviewUtils extends Script { | ||||||
|         return { ...result, context } |         return { ...result, context } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private getAllLayerConfigs(): LayerConfigJson[] { | ||||||
|  |         const allPaths = ScriptUtils.getLayerPaths() | ||||||
|  |         const results: LayerConfigJson[] = [] | ||||||
|  |         for (let i = 0; i < allPaths.length; i++) { | ||||||
|  |             const path = allPaths[i] | ||||||
|  |             ScriptUtils.erasableLog(`Parsing layerConfig ${i + 1}/${allPaths.length}: ${path}                   `) | ||||||
|  |             const data = JSON.parse(readFileSync(path, "utf8")) | ||||||
|  |             results.push(data) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         return results | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private buildLayerIndex( |     private buildLayerIndex( | ||||||
|         doesImageExist: DoesImageExist, |         doesImageExist: DoesImageExist | ||||||
|         forceReload: boolean, |  | ||||||
|         whitelist: Set<string> |  | ||||||
|     ): Map<string, LayerConfigJson> { |     ): Map<string, LayerConfigJson> { | ||||||
|         // First, we expand and validate all builtin layers. These are written to src/assets/generated/layers
 |         // First, we expand and validate all builtin layers. These are written to src/assets/generated/layers
 | ||||||
|         // At the same time, an index of available layers is built.
 |         // At the same time, an index of available layers is built.
 | ||||||
|         console.log("------------- VALIDATING THE BUILTIN QUESTIONS ---------------") |         const sharedQuestions = this.getSharedTagRenderings(doesImageExist) | ||||||
|         const sharedTagRenderings = this.getSharedTagRenderings(doesImageExist) |         const allLayerConfigs = this.getAllLayerConfigs() | ||||||
|         console.log("   ---------- VALIDATING BUILTIN LAYERS ---------") |         const sharedQuestionsDef = allLayerConfigs.find(l => l.id === "questions") | ||||||
|         const state: DesugaringContext = { |         sharedQuestionsDef.tagRenderings = sharedQuestions | ||||||
|             tagRenderings: LayerOverviewUtils.asDict(sharedTagRenderings), | 
 | ||||||
|             tagRenderingOrder: sharedTagRenderings.map((tr) => tr.id), | 
 | ||||||
|             sharedLayers: AllSharedLayers.getSharedLayersConfigs(), |         const dependencyGraph = LayerConfigDependencyGraph.buildDirectDependencies(allLayerConfigs) | ||||||
|         } |         const levels = LayerConfigDependencyGraph.buildLevels(dependencyGraph) | ||||||
|         const sharedLayers = new Map<string, LayerConfigJson>() |         const layerState = new Map<string, "clean" | "dirty" | "changed">() | ||||||
|         const prepLayer = new PrepareLayer(state) |         console.log("# BUILD PLAN\n\n") | ||||||
|         const skippedLayers: string[] = [] |         for (const levelInfo of levels) { | ||||||
|         const recompiledLayers: string[] = [] |             console.log(`## LEVEL ${levelInfo.level}${levelInfo.loop ? " (LOOP)" : ""}`) | ||||||
|         let warningCount = 0 |             for (const id of levelInfo.ids) { | ||||||
|         for (const sharedLayerPath of ScriptUtils.getLayerPaths()) { |                 const deps = dependencyGraph.get(id) ?? [] | ||||||
|             if (whitelist.size > 0) { |                 const dirtyDeps = deps.filter(dep => { | ||||||
|                 const idByPath = sharedLayerPath.split("/").at(-1).split(".")[0] |                     const depState = layerState.get(dep) | ||||||
|                 if (!Constants.isPriviliged(idByPath) && !whitelist.has(idByPath)) { |                     if (levelInfo.loop && depState === undefined) { | ||||||
|                     continue |                         const depIsClean = | ||||||
|                 } |                             LayerOverviewUtils.shouldBeUpdated( | ||||||
|             } |                                 LayerBuilder.sourcePath(dep), | ||||||
|             { |                                 LayerBuilder.targetPath(dep)) | ||||||
|                 const targetPath = |                         if (depIsClean) { | ||||||
|                     LayerOverviewUtils.layerPath + |                             return false | ||||||
|                     sharedLayerPath.substring(sharedLayerPath.lastIndexOf("/")) |                         } | ||||||
|                 if (!forceReload && !this.shouldBeUpdated(sharedLayerPath, targetPath)) { |                     } | ||||||
|                     try { |                     return depState !== "clean" | ||||||
|                         const sharedLayer = JSON.parse(readFileSync(targetPath, "utf8")) |                 }) | ||||||
|                         sharedLayers.set(sharedLayer.id, sharedLayer) |                 if (dirtyDeps.length > 0) { | ||||||
|                         skippedLayers.push(sharedLayer.id) |                     layerState.set(id, "dirty") | ||||||
|                         continue |                 } else { | ||||||
|                     } catch (e) { | 
 | ||||||
|                         throw "Could not parse " + targetPath + " : " + e | 
 | ||||||
|  |                     const sourcePath = `./assets/layers/${id}/${id}.json` | ||||||
|  |                     const targetPath = `./public/assets/generated/layers/${id}.json` | ||||||
|  | 
 | ||||||
|  |                     if (id === "questions") { | ||||||
|  |                         layerState.set(id, "clean") | ||||||
|  |                     } else if (LayerOverviewUtils.shouldBeUpdated(sourcePath, targetPath)) { | ||||||
|  |                         layerState.set(id, "changed") | ||||||
|  |                     } else { | ||||||
|  |                         layerState.set(id, "clean") | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |                 const state = layerState.get(id) | ||||||
|  |                 console.log(`- ${id} (${state}; ${dirtyDeps.map(dd => dd + "*").join(", ")})`) | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             const parsed = this.parseLayer(doesImageExist, prepLayer, sharedLayerPath) |  | ||||||
|             warningCount += parsed.context.getAll("warning").length |  | ||||||
|             const fixed = parsed.raw |  | ||||||
|             if (sharedLayers.has(fixed.id)) { |  | ||||||
|                 throw "There are multiple layers with the id " + fixed.id + ", " + sharedLayerPath |  | ||||||
|             } |  | ||||||
|             if (parsed.context.hasErrors()) { |  | ||||||
|                 throw "Some layers contain errors" |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             sharedLayers.set(fixed.id, fixed) |  | ||||||
|             recompiledLayers.push(fixed.id) |  | ||||||
| 
 |  | ||||||
|             this.writeLayer(fixed) |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         console.log( | 
 | ||||||
|             "Recompiled layers " + |         const builder = new LayerBuilder(allLayerConfigs, dependencyGraph, levels, layerState, sharedQuestions) | ||||||
|                 recompiledLayers.join(", ") + |         builder.writeLayer(sharedQuestionsDef) | ||||||
|                 " and skipped " + |         const allLayers = builder.convertStrict({}, ConversionContext.construct([], [])) | ||||||
|                 skippedLayers.length + |         if (layerState.get("usersettings") !== "clean") { | ||||||
|                 " layers. Detected " + |             // We always need the calculated tags of 'usersettings', so we export them separately if dirty
 | ||||||
|                 warningCount + | 
 | ||||||
|                 " warnings" |             LayerOverviewUtils.extractJavascriptCodeForLayer( | ||||||
|         ) |                 allLayers.get("usersettings"), | ||||||
|         // We always need the calculated tags of 'usersettings', so we export them separately
 |  | ||||||
|         this.extractJavascriptCodeForLayer( |  | ||||||
|             state.sharedLayers.get("usersettings"), |  | ||||||
|             "./src/Logic/State/UserSettingsMetaTagging.ts" |             "./src/Logic/State/UserSettingsMetaTagging.ts" | ||||||
|         ) |         ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return allLayers | ||||||
| 
 | 
 | ||||||
|         return sharedLayers |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -741,7 +888,7 @@ class LayerOverviewUtils extends Script { | ||||||
|         writeFileSync(targetDir + themeFile.id + ".ts", allCode.join("\n")) |         writeFileSync(targetDir + themeFile.id + ".ts", allCode.join("\n")) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private extractJavascriptCodeForLayer(l: LayerConfigJson, targetPath?: string) { |     public static extractJavascriptCodeForLayer(l: LayerConfigJson, targetPath?: string) { | ||||||
|         if (!l) { |         if (!l) { | ||||||
|             return // Probably a bootstrapping run
 |             return // Probably a bootstrapping run
 | ||||||
|         } |         } | ||||||
|  | @ -858,7 +1005,7 @@ class LayerOverviewUtils extends Script { | ||||||
|                 LayerOverviewUtils.extractLayerIdsFrom(themeFile, false) |                 LayerOverviewUtils.extractLayerIdsFrom(themeFile, false) | ||||||
|             ).map((id) => LayerOverviewUtils.layerPath + id + ".json") |             ).map((id) => LayerOverviewUtils.layerPath + id + ".json") | ||||||
| 
 | 
 | ||||||
|             if (!forceReload && !this.shouldBeUpdated([themePath, ...usedLayers], targetPath)) { |             if (!forceReload && !LayerOverviewUtils.shouldBeUpdated([themePath, ...usedLayers], targetPath)) { | ||||||
|                 fixed.set( |                 fixed.set( | ||||||
|                     themeFile.id, |                     themeFile.id, | ||||||
|                     JSON.parse( |                     JSON.parse( | ||||||
|  |  | ||||||
|  | @ -5,12 +5,12 @@ | ||||||
| mkdir -p ./src/assets/generated/layers | mkdir -p ./src/assets/generated/layers | ||||||
| mkdir -p ./public/assets/generated/themes | mkdir -p ./public/assets/generated/themes | ||||||
| echo '{"layers": []}' > ./src/assets/generated/known_layers.json | echo '{"layers": []}' > ./src/assets/generated/known_layers.json | ||||||
| rm -f ./src/assets/generated/layers/*.json | rm -f ./public/assets/generated/layers/*.json | ||||||
| rm -f ./public/assets/generated/themes/*.json | rm -f ./public/assets/generated/themes/*.json | ||||||
| cp ./assets/layers/usersettings/usersettings.json ./src/assets/generated/layers/usersettings.json | echo '{}' > ./public/assets/generated/layers/favourite.json | ||||||
| echo '{}' > ./src/assets/generated/layers/favourite.json | echo '{}' > ./public/assets/generated/layers/summary.json | ||||||
| echo '{}' > ./src/assets/generated/layers/summary.json | echo '{}' > ./public/assets/generated/layers/last_click.json | ||||||
| echo '{}' > ./src/assets/generated/layers/last_click.json | echo '{}' > ./public/assets/generated/layers/search.json | ||||||
| echo '{}' > ./src/assets/generated/layers/search.json | echo '[]' > ./public/assets/generated/theme_overview.json | ||||||
| echo '[]' > ./src/assets/generated/theme_overview.json | echo '{}' > ./public/assets/generated/layers/geocoded_image.json | ||||||
| echo '{}' > ./src/assets/generated/layers/geocoded_image.json | echo '{}' > ./public/assets/generated/layers/usersettings.json | ||||||
|  |  | ||||||
|  | @ -20,8 +20,6 @@ npm run download:editor-layer-index && | ||||||
| npm run prep:layeroverview && | npm run prep:layeroverview && | ||||||
| npm run generate && # includes a single "refresh:layeroverview". Resetting the files is unnecessary as they are not in there in the first place | npm run generate && # includes a single "refresh:layeroverview". Resetting the files is unnecessary as they are not in there in the first place | ||||||
| npm run generate:mapcomplete-changes-theme  && | npm run generate:mapcomplete-changes-theme  && | ||||||
| npm run refresh:layeroverview && #  a second time to propagate all calls |  | ||||||
| npm run refresh:layeroverview &&  # a third time to fix some issues with the favourite layer all calls |  | ||||||
| npm run generate:layouts | npm run generate:layouts | ||||||
| 
 | 
 | ||||||
| if [ $? -ne 0 ]; then | if [ $? -ne 0 ]; then | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import ThemeConfig from "../Models/ThemeConfig/ThemeConfig" | import ThemeConfig from "../Models/ThemeConfig/ThemeConfig" | ||||||
| import favourite from "../assets/generated/layers/favourite.json" | import favourite from "../../public/assets/generated/layers/favourite.json" | ||||||
| import { ThemeConfigJson } from "../Models/ThemeConfig/Json/ThemeConfigJson" | import { ThemeConfigJson } from "../Models/ThemeConfig/Json/ThemeConfigJson" | ||||||
| import { AllSharedLayers } from "./AllSharedLayers" | import { AllSharedLayers } from "./AllSharedLayers" | ||||||
| import Constants from "../Models/Constants" | import Constants from "../Models/Constants" | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme" | ||||||
| import licenses from "../assets/generated/license_info.json" | import licenses from "../assets/generated/license_info.json" | ||||||
| import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" | import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" | ||||||
| import { FixImages } from "../Models/ThemeConfig/Conversion/FixImages" | import { FixImages } from "../Models/ThemeConfig/Conversion/FixImages" | ||||||
| import questions from "../assets/generated/layers/questions.json" | import questions from "../../public/assets/generated/layers/questions.json" | ||||||
| import { DoesImageExist, PrevalidateTheme } from "../Models/ThemeConfig/Conversion/Validation" | import { DoesImageExist, PrevalidateTheme } from "../Models/ThemeConfig/Conversion/Validation" | ||||||
| import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion" | import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion" | ||||||
| import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson" | import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson" | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { BBox } from "../BBox" | ||||||
| import { Feature, Geometry } from "geojson" | import { Feature, Geometry } from "geojson" | ||||||
| import { DefaultPinIcon } from "../../Models/Constants" | import { DefaultPinIcon } from "../../Models/Constants" | ||||||
| import { Store } from "../UIEventSource" | import { Store } from "../UIEventSource" | ||||||
| import * as search from "../../assets/generated/layers/search.json" | import * as search from "../../../public/assets/generated/layers/search.json" | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||||
| import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | ||||||
| import { GeoOperations } from "../GeoOperations" | import { GeoOperations } from "../GeoOperations" | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ import translators from "../../assets/translators.json" | ||||||
| import codeContributors from "../../assets/contributors.json" | import codeContributors from "../../assets/contributors.json" | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||||
| import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | ||||||
| import usersettings from "../../../src/assets/generated/layers/usersettings.json" | import usersettings from "../../../public/assets/generated/layers/usersettings.json" | ||||||
| import Locale from "../../UI/i18n/Locale" | import Locale from "../../UI/i18n/Locale" | ||||||
| import LinkToWeblate from "../../UI/Base/LinkToWeblate" | import LinkToWeblate from "../../UI/Base/LinkToWeblate" | ||||||
| import FeatureSwitchState from "./FeatureSwitchState" | import FeatureSwitchState from "./FeatureSwitchState" | ||||||
|  |  | ||||||
|  | @ -1,42 +1,14 @@ | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| /** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */ | /** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */ | ||||||
| export class ThemeMetaTagging { | export class ThemeMetaTagging { | ||||||
|     public static readonly themeName = "usersettings" |    public static readonly themeName = "usersettings" | ||||||
| 
 | 
 | ||||||
|     public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) { |    public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) { | ||||||
|         Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () => |       Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )  | ||||||
|             feat.properties._description |       Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? '' )  | ||||||
|                 .match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/) |       Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href   }) (feat)  )  | ||||||
|                 ?.at(1) |       Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat)  )  | ||||||
|         ) |       Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )  | ||||||
|         Utils.AddLazyProperty( |       feat.properties['__current_backgroun'] = 'initial_value' | ||||||
|             feat.properties, |    } | ||||||
|             "_d", |  | ||||||
|             () => feat.properties._description?.replace(/</g, "<")?.replace(/>/g, ">") ?? "" |  | ||||||
|         ) |  | ||||||
|         Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () => |  | ||||||
|             ((feat) => { |  | ||||||
|                 const e = document.createElement("div") |  | ||||||
|                 e.innerHTML = feat.properties._d |  | ||||||
|                 return Array.from(e.getElementsByTagName("a")).filter( |  | ||||||
|                     (a) => a.href.match(/mastodon|en.osm.town/) !== null |  | ||||||
|                 )[0]?.href |  | ||||||
|             })(feat) |  | ||||||
|         ) |  | ||||||
|         Utils.AddLazyProperty(feat.properties, "_mastodon_link", () => |  | ||||||
|             ((feat) => { |  | ||||||
|                 const e = document.createElement("div") |  | ||||||
|                 e.innerHTML = feat.properties._d |  | ||||||
|                 return Array.from(e.getElementsByTagName("a")).filter( |  | ||||||
|                     (a) => a.getAttribute("rel")?.indexOf("me") >= 0 |  | ||||||
|                 )[0]?.href |  | ||||||
|             })(feat) |  | ||||||
|         ) |  | ||||||
|         Utils.AddLazyProperty( |  | ||||||
|             feat.properties, |  | ||||||
|             "_mastodon_candidate", |  | ||||||
|             () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a |  | ||||||
|         ) |  | ||||||
|         feat.properties["__current_backgroun"] = "initial_value" |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | @ -239,11 +239,11 @@ export class ExpandFilter extends DesugaringStep<LayerConfigJson> { | ||||||
|                     ) |                     ) | ||||||
|                 } |                 } | ||||||
|                 const layer = this._state.sharedLayers.get(split[0]) |                 const layer = this._state.sharedLayers.get(split[0]) | ||||||
|                 if (layer === undefined) { |                 if (!layer) { | ||||||
|                     context.err("Layer '" + split[0] + "' not found") |                     context.err("Layer '" + split[0] + "' not found") | ||||||
|                 } |                 } | ||||||
|                 const expectedId = split[1] |                 const expectedId = split[1] | ||||||
|                 const expandedFilter = (<(FilterConfigJson | string)[]>layer.filter).find( |                 const expandedFilter = (<(FilterConfigJson | string)[]>layer?.filter)?.find( | ||||||
|                     (f) => typeof f !== "string" && f.id === expectedId |                     (f) => typeof f !== "string" && f.id === expectedId | ||||||
|                 ) |                 ) | ||||||
|                 if (expandedFilter === undefined) { |                 if (expandedFilter === undefined) { | ||||||
|  |  | ||||||
|  | @ -380,15 +380,10 @@ export class ExpandTagRendering extends Conversion< | ||||||
|                                 Utils.NoNull(Array.from(state.sharedLayers.keys())), |                                 Utils.NoNull(Array.from(state.sharedLayers.keys())), | ||||||
|                                 (s) => s |                                 (s) => s | ||||||
|                             ) |                             ) | ||||||
|                             if (state.sharedLayers.size === 0) { |                             if (candidates.length === 0) { | ||||||
|                                 ctx.warn( |                                 ctx.err("While reusing a tagRendering: " + name + "; no candidates in layer " + layerName) | ||||||
|                                     "BOOTSTRAPPING. Rerun generate layeroverview. While reusing tagrendering: " + |  | ||||||
|                                         name + |  | ||||||
|                                         ": layer " + |  | ||||||
|                                         layerName + |  | ||||||
|                                         " not found for now, but ignoring as this is a bootstrapping run. " |  | ||||||
|                                 ) |  | ||||||
|                             } else { |                             } else { | ||||||
|  |                                 console.error("Bench was not found...") | ||||||
|                                 ctx.err( |                                 ctx.err( | ||||||
|                                     ": While reusing tagrendering: " + |                                     ": While reusing tagrendering: " + | ||||||
|                                         name + |                                         name + | ||||||
|  | @ -400,10 +395,15 @@ export class ExpandTagRendering extends Conversion< | ||||||
|                             } |                             } | ||||||
|                             continue |                             continue | ||||||
|                         } |                         } | ||||||
|  |                         if (layer.source === "special:stub") { | ||||||
|  |                             // We are dealing with a looping import, no error is necessary
 | ||||||
|  |                             continue | ||||||
|  |                         } | ||||||
|                         candidates = Utils.NoNull(layer.tagRenderings.map((tr) => tr["id"])).map( |                         candidates = Utils.NoNull(layer.tagRenderings.map((tr) => tr["id"])).map( | ||||||
|                             (id) => layerName + "." + id |                             (id) => layerName + "." + id | ||||||
|                         ) |                         ) | ||||||
|                     } |                     } | ||||||
|  | 
 | ||||||
|                     candidates = Utils.sortedByLevenshteinDistance(name, candidates, (i) => i) |                     candidates = Utils.sortedByLevenshteinDistance(name, candidates, (i) => i) | ||||||
|                     ctx.err( |                     ctx.err( | ||||||
|                         "The tagRendering with identifier " + |                         "The tagRendering with identifier " + | ||||||
|  |  | ||||||
|  | @ -1048,6 +1048,11 @@ export class PrepareLayer extends Fuse<LayerConfigJson> { | ||||||
|         if (json === undefined || json === null) { |         if (json === undefined || json === null) { | ||||||
|             throw "Error: prepareLayer got null" |             throw "Error: prepareLayer got null" | ||||||
|         } |         } | ||||||
|  |         if (json.source?.["osmTags"] !== undefined && json.source?.["osmTags"]?.["and"] === undefined) { | ||||||
|  |             json = { ...json } | ||||||
|  |             json.source = <any>{ ...(<object>json.source) } | ||||||
|  |             json.source["osmTags"] = { "and": [json.source["osmTags"]] } | ||||||
|  |         } | ||||||
|         return super.convert(json, context) |         return super.convert(json, context) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -78,6 +78,7 @@ export interface LayerConfigJson { | ||||||
|         | undefined |         | undefined | ||||||
|         | "special" |         | "special" | ||||||
|         | "special:library" |         | "special:library" | ||||||
|  |         | "special:stub" // only used when building looping imports
 | ||||||
|         | { |         | { | ||||||
|               /** |               /** | ||||||
|                * question: Which tags must be present on the feature to show it in this layer? |                * question: Which tags must be present on the feature to show it in this layer? | ||||||
|  |  | ||||||
|  | @ -11,14 +11,14 @@ import MetaTagging from "../../Logic/MetaTagging" | ||||||
| import FilteredLayer from "../FilteredLayer" | import FilteredLayer from "../FilteredLayer" | ||||||
| import LayerConfig from "../ThemeConfig/LayerConfig" | import LayerConfig from "../ThemeConfig/LayerConfig" | ||||||
| import { LayerConfigJson } from "../ThemeConfig/Json/LayerConfigJson" | import { LayerConfigJson } from "../ThemeConfig/Json/LayerConfigJson" | ||||||
| import last_click_layerconfig from "../../assets/generated/layers/last_click.json" | import last_click_layerconfig from "../../../public/assets/generated/layers/last_click.json" | ||||||
| import { GeoOperations } from "../../Logic/GeoOperations" | import { GeoOperations } from "../../Logic/GeoOperations" | ||||||
| import summaryLayer from "../../assets/generated/layers/summary.json" | import summaryLayer from "../../../public/assets/generated/layers/summary.json" | ||||||
| import { Store, UIEventSource } from "../../Logic/UIEventSource" | import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||||
| import NearbyFeatureSource from "../../Logic/FeatureSource/Sources/NearbyFeatureSource" | import NearbyFeatureSource from "../../Logic/FeatureSource/Sources/NearbyFeatureSource" | ||||||
| import { | import { | ||||||
|     SummaryTileSource, |     SummaryTileSource, | ||||||
|     SummaryTileSourceRewriter, |     SummaryTileSourceRewriter | ||||||
| } from "../../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource" | } from "../../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource" | ||||||
| import { ShowDataLayerOptions } from "../../UI/Map/ShowDataLayerOptions" | import { ShowDataLayerOptions } from "../../UI/Map/ShowDataLayerOptions" | ||||||
| 
 | 
 | ||||||
|  | @ -195,7 +195,6 @@ export class WithSpecialLayers extends WithChangesState { | ||||||
|                   | "range" // handled by UserMapFeatureSwitchState
 |                   | "range" // handled by UserMapFeatureSwitchState
 | ||||||
|                   | "selected_element" // handled by this.drawSelectedElement
 |                   | "selected_element" // handled by this.drawSelectedElement
 | ||||||
|               > |               > | ||||||
|         const empty = [] |  | ||||||
|         /** |         /** | ||||||
|          * A listing which maps the layerId onto the featureSource |          * A listing which maps the layerId onto the featureSource | ||||||
|          */ |          */ | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ | ||||||
|   import SelectedElementView from "./SelectedElementView.svelte" |   import SelectedElementView from "./SelectedElementView.svelte" | ||||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" |   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||||
|   import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" |   import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | ||||||
|   import usersettings from "../../assets/generated/layers/usersettings.json" |   import usersettings from "../../../public/assets/generated/layers/usersettings.json" | ||||||
|   import UserRelatedState from "../../Logic/State/UserRelatedState" |   import UserRelatedState from "../../Logic/State/UserRelatedState" | ||||||
|   import ArrowDownTray from "@babeard/svelte-heroicons/mini/ArrowDownTray" |   import ArrowDownTray from "@babeard/svelte-heroicons/mini/ArrowDownTray" | ||||||
|   import DownloadPanel from "../DownloadFlow/DownloadPanel.svelte" |   import DownloadPanel from "../DownloadFlow/DownloadPanel.svelte" | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ | ||||||
|   import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" |   import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" | ||||||
|   import ShowDataLayer from "../Map/ShowDataLayer" |   import ShowDataLayer from "../Map/ShowDataLayer" | ||||||
|   import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" |   import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" | ||||||
|   import * as geocoded_image from "../../assets/generated/layers/geocoded_image.json" |   import * as geocoded_image from "../../../public/assets/generated/layers/geocoded_image.json" | ||||||
|   import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" |   import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | ||||||
|   import { onDestroy } from "svelte" |   import { onDestroy } from "svelte" | ||||||
|   import { BBox } from "../../Logic/BBox" |   import { BBox } from "../../Logic/BBox" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue