forked from MapComplete/MapComplete
		
	Update of latlon2country and use its async interface; small refactoring of simplemetagging, improvements to cacheBuilder which respects isShown and calculated tags now
This commit is contained in:
		
							parent
							
								
									e053e9f279
								
							
						
					
					
						commit
						9cfb7fbe68
					
				
					 14 changed files with 417 additions and 4320 deletions
				
			
		
							
								
								
									
										3
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							|  | @ -1,3 +0,0 @@ | |||
| { | ||||
|   "files.eol": "\n" | ||||
| } | ||||
|  | @ -68,7 +68,7 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti | |||
|         const features: { feature: any; freshness: Date }[] = this.upstream.features.data; | ||||
|         const newFeatures = features.filter((f) => { | ||||
| 
 | ||||
|             self.registerCallback(f.feature, layer.layerDef) | ||||
|             self.registerCallback(f.feature) | ||||
| 
 | ||||
|             if ( | ||||
|                 this.state.selectedElement.data?.id === f.feature.id || | ||||
|  | @ -105,15 +105,18 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti | |||
|         this._is_dirty.setData(false) | ||||
|     } | ||||
| 
 | ||||
|     private registerCallback(feature: any, layer: LayerConfig) { | ||||
|         const src = this.state.allElements.addOrGetElement(feature) | ||||
|     private registerCallback(feature: any) { | ||||
|         const src = this.state?.allElements?.addOrGetElement(feature) | ||||
|         if(src == undefined){ | ||||
|             return | ||||
|         } | ||||
|         if (this._alreadyRegistered.has(src)) { | ||||
|             return | ||||
|         } | ||||
|         this._alreadyRegistered.add(src) | ||||
| 
 | ||||
|             const self = this; | ||||
|             src.addCallbackAndRunD(isShown => { | ||||
|             src.addCallbackAndRunD(_ => { | ||||
|                 self._is_dirty.setData(true) | ||||
|             }) | ||||
|     } | ||||
|  |  | |||
|  | @ -86,6 +86,10 @@ export default class TiledFeatureSource implements Tiled, IndexedFeatureSource, | |||
|     } | ||||
| 
 | ||||
|     public static createHierarchy(features: FeatureSource, options?: TiledFeatureSourceOptions): TiledFeatureSource { | ||||
|         options = { | ||||
|             ...options, | ||||
|             layer: features["layer"] ?? options.layer | ||||
|         } | ||||
|         const root = new TiledFeatureSource(0, 0, 0, null, options) | ||||
|         features.features?.addCallbackAndRunD(feats => root.addFeatures(feats)) | ||||
|         return root; | ||||
|  | @ -200,6 +204,6 @@ export interface TiledFeatureSourceOptions { | |||
|      * Setting 'dontEnforceMinZoomLevel' will still allow bigger zoom levels for those features | ||||
|      */ | ||||
|     readonly dontEnforceMinZoom?: boolean, | ||||
|     readonly registerTile?: (tile: TiledFeatureSource & Tiled) => void, | ||||
|     readonly registerTile?: (tile: TiledFeatureSource & FeatureSourceForLayer & Tiled) => void, | ||||
|     readonly layer?: FilteredLayer | ||||
| } | ||||
|  | @ -25,8 +25,7 @@ export class GeoOperations { | |||
|     } | ||||
| 
 | ||||
|     static centerpointCoordinates(feature: any): [number, number] { | ||||
|         // @ts-ignore
 | ||||
|         return turf.center(feature).geometry.coordinates; | ||||
|         return <[number, number]> turf.center(feature).geometry.coordinates; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -35,7 +34,7 @@ export class GeoOperations { | |||
|      * @param lonlat1 | ||||
|      */ | ||||
|     static distanceBetween(lonlat0: [number, number], lonlat1: [number, number]) { | ||||
|         return turf.distance(lonlat0, lonlat1) * 1000 | ||||
|         return turf.distance(lonlat0, lonlat1, {units: "meters"}) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -196,8 +195,8 @@ export class GeoOperations { | |||
| 
 | ||||
|     static buffer(feature: any, bufferSizeInMeter: number) { | ||||
|         return turf.buffer(feature, bufferSizeInMeter / 1000, { | ||||
|             units: 'kilometers' | ||||
|         }) | ||||
|             units:'kilometers' | ||||
|         } ) | ||||
|     } | ||||
| 
 | ||||
|     static bbox(feature: any) { | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import SimpleMetaTagger from "./SimpleMetaTagger"; | ||||
| import SimpleMetaTaggers from "./SimpleMetaTagger"; | ||||
| import {ExtraFuncParams, ExtraFunctions} from "./ExtraFunctions"; | ||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||
| import State from "../State"; | ||||
|  | @ -33,9 +33,8 @@ export default class MetaTagging { | |||
|             return; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         const metatagsToApply: SimpleMetaTagger [] = [] | ||||
|         for (const metatag of SimpleMetaTagger.metatags) { | ||||
|         const metatagsToApply: SimpleMetaTaggers[] = [] | ||||
|         for (const metatag of SimpleMetaTaggers.metatags) { | ||||
|             if (metatag.includesDates) { | ||||
|                 if (options.includeDates ?? true) { | ||||
|                     metatagsToApply.push(metatag) | ||||
|  | @ -59,19 +58,23 @@ export default class MetaTagging { | |||
|             let somethingChanged = false | ||||
|             for (const metatag of metatagsToApply) { | ||||
|                 try { | ||||
|                     // @ts-ignore
 | ||||
|                     if (!metatag.keys.some(key => feature.properties[key] === undefined)) { | ||||
|                         // All keys are already defined, we probably already ran this one
 | ||||
|                         continue | ||||
|                     } | ||||
| 
 | ||||
|                     // @ts-ignore
 | ||||
|                     if (metatag.isLazy) { | ||||
|                         somethingChanged = true; | ||||
| 
 | ||||
|                         // @ts-ignore
 | ||||
|                         metatag.applyMetaTagsOnFeature(feature, freshness, layer) | ||||
| 
 | ||||
|                          | ||||
|                     } else { | ||||
| 
 | ||||
| 
 | ||||
|                         // @ts-ignore
 | ||||
|                         const newValueAdded = metatag.applyMetaTagsOnFeature(feature, freshness, layer) | ||||
|                         /* Note that the expression: | ||||
|                         * `somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness)` | ||||
|  | @ -83,6 +86,7 @@ export default class MetaTagging { | |||
|                         somethingChanged = newValueAdded || somethingChanged | ||||
|                     } | ||||
|                 } catch (e) { | ||||
|                     // @ts-ignore
 | ||||
|                     console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack) | ||||
|                 } | ||||
|             } | ||||
|  | @ -117,8 +121,9 @@ export default class MetaTagging { | |||
|             const func = new Function("feat", "return " + code + ";"); | ||||
| 
 | ||||
|             const f = (feature: any) => { | ||||
|                  | ||||
|                  | ||||
|                 delete feature.properties[key] | ||||
| 
 | ||||
|                 Object.defineProperty(feature.properties, key, { | ||||
|                     configurable: true, | ||||
|                     enumerable: false, // By setting this as not enumerable, the localTileSaver will _not_ calculate this
 | ||||
|  | @ -149,7 +154,6 @@ export default class MetaTagging { | |||
| 
 | ||||
|                     } | ||||
|                 }) | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,18 +7,92 @@ import BaseUIElement from "../UI/BaseUIElement"; | |||
| import Title from "../UI/Base/Title"; | ||||
| import {FixedUiElement} from "../UI/Base/FixedUiElement"; | ||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||
| import {CountryCoder} from "latlon2country" | ||||
| 
 | ||||
| 
 | ||||
| const cardinalDirections = { | ||||
|     N: 0, NNE: 22.5, NE: 45, ENE: 67.5, | ||||
|     E: 90, ESE: 112.5, SE: 135, SSE: 157.5, | ||||
|     S: 180, SSW: 202.5, SW: 225, WSW: 247.5, | ||||
|     W: 270, WNW: 292.5, NW: 315, NNW: 337.5 | ||||
| export class SimpleMetaTagger  { | ||||
|     public readonly keys: string[]; | ||||
|     public readonly doc: string; | ||||
|     public readonly isLazy: boolean; | ||||
|     public readonly includesDates: boolean | ||||
|     public readonly applyMetaTagsOnFeature: (feature: any, freshness: Date, layer: LayerConfig) => boolean; | ||||
| 
 | ||||
|     /*** | ||||
|      * A function that adds some extra data to a feature | ||||
|      * @param docs: what does this extra data do? | ||||
|      * @param f: apply the changes. Returns true if something changed | ||||
|      */ | ||||
|     constructor(docs: { keys: string[], doc: string, includesDates?: boolean, isLazy?: boolean, cleanupRetagger?: boolean }, | ||||
|                 f: ((feature: any, freshness: Date, layer: LayerConfig) => boolean)) { | ||||
|         this.keys = docs.keys; | ||||
|         this.doc = docs.doc; | ||||
|         this.isLazy = docs.isLazy | ||||
|         this.applyMetaTagsOnFeature = f; | ||||
|         this.includesDates = docs.includesDates ?? false; | ||||
|         if (!docs.cleanupRetagger) { | ||||
|             for (const key of docs.keys) { | ||||
|                 if (!key.startsWith('_') && key.toLowerCase().indexOf("theme") < 0) { | ||||
|                     throw `Incorrect metakey ${key}: it should start with underscore (_)` | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export class CountryTagger extends SimpleMetaTagger { | ||||
|     private static readonly coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/"); | ||||
| 
 | ||||
|     public runningTasks: Set<any>; | ||||
|      | ||||
|     constructor() { | ||||
|         const runningTasks=  new Set<any>(); | ||||
|         super | ||||
|         ( | ||||
|             { | ||||
|                 keys: ["_country"], | ||||
|                 doc: "The country code of the property (with latlon2country)", | ||||
|                 includesDates: false | ||||
|             }, | ||||
|             ((feature, _) => { | ||||
|                 let centerPoint: any = GeoOperations.centerpoint(feature); | ||||
|                 const lat = centerPoint.geometry.coordinates[1]; | ||||
|                 const lon = centerPoint.geometry.coordinates[0]; | ||||
|                 runningTasks.add(feature) | ||||
|                 CountryTagger.coder.GetCountryCodeAsync(lon, lat).then( | ||||
|                     countries => { | ||||
|                         runningTasks.delete(feature) | ||||
|                         try { | ||||
|                             const oldCountry = feature.properties["_country"]; | ||||
|                             feature.properties["_country"] = countries[0].trim().toLowerCase(); | ||||
|                             if (oldCountry !== feature.properties["_country"]) { | ||||
|                                 const tagsSource = State.state?.allElements?.getEventSourceById(feature.properties.id); | ||||
|                                 tagsSource?.ping(); | ||||
|                             } | ||||
|                         } catch (e) { | ||||
|                             console.warn(e) | ||||
|                         } | ||||
|                     } | ||||
|                 ).catch(_ => { | ||||
|                     runningTasks.delete(feature) | ||||
|                 }) | ||||
|                 return false; | ||||
|             }) | ||||
|         ) | ||||
|             this.runningTasks   = runningTasks; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export default class SimpleMetaTaggers { | ||||
| 
 | ||||
|     private static readonly cardinalDirections = { | ||||
|         N: 0, NNE: 22.5, NE: 45, ENE: 67.5, | ||||
|         E: 90, ESE: 112.5, SE: 135, SSE: 157.5, | ||||
|         S: 180, SSW: 202.5, SW: 225, WSW: 247.5, | ||||
|         W: 270, WNW: 292.5, NW: 315, NNW: 337.5 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| export default class SimpleMetaTagger { | ||||
|     public static coder: any; | ||||
|     public static readonly objectMetaInfo = new SimpleMetaTagger( | ||||
|         { | ||||
|             keys: ["_last_edit:contributor", | ||||
|  | @ -92,7 +166,7 @@ export default class SimpleMetaTagger { | |||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             return SimpleMetaTagger.removeBothTagging(feature.properties) | ||||
|             return SimpleMetaTaggers.removeBothTagging(feature.properties) | ||||
|         }) | ||||
|     ) | ||||
|     private static surfaceArea = new SimpleMetaTagger( | ||||
|  | @ -197,32 +271,7 @@ export default class SimpleMetaTagger { | |||
|             return true; | ||||
|         }) | ||||
|     ) | ||||
|     private static country = new SimpleMetaTagger( | ||||
|         { | ||||
|             keys: ["_country"], | ||||
|             doc: "The country code of the property (with latlon2country)", | ||||
|             includesDates: false | ||||
|         }, | ||||
|         ((feature, _) => { | ||||
|             let centerPoint: any = GeoOperations.centerpoint(feature); | ||||
|             const lat = centerPoint.geometry.coordinates[1]; | ||||
|             const lon = centerPoint.geometry.coordinates[0]; | ||||
| 
 | ||||
|             SimpleMetaTagger.coder?.GetCountryCodeFor(lon, lat, (countries: string[]) => { | ||||
|                 try { | ||||
|                     const oldCountry = feature.properties["_country"]; | ||||
|                     feature.properties["_country"] = countries[0].trim().toLowerCase(); | ||||
|                     if (oldCountry !== feature.properties["_country"]) { | ||||
|                         const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id); | ||||
|                         tagsSource.ping(); | ||||
|                     } | ||||
|                 } catch (e) { | ||||
|                     console.warn(e) | ||||
|                 } | ||||
|             }) | ||||
|             return false; | ||||
|         }) | ||||
|     ) | ||||
|     public static country = new CountryTagger() | ||||
|     private static isOpen = new SimpleMetaTagger( | ||||
|         { | ||||
|             keys: ["_isOpen", "_isOpen:description"], | ||||
|  | @ -319,7 +368,7 @@ export default class SimpleMetaTagger { | |||
|             if (direction === undefined) { | ||||
|                 return false; | ||||
|             } | ||||
|             const n = cardinalDirections[direction] ?? Number(direction); | ||||
|             const n = SimpleMetaTaggers.cardinalDirections[direction] ?? Number(direction); | ||||
|             if (isNaN(n)) { | ||||
|                 return false; | ||||
|             } | ||||
|  | @ -333,6 +382,7 @@ export default class SimpleMetaTagger { | |||
|         }) | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
|     private static currentTime = new SimpleMetaTagger( | ||||
|         { | ||||
|             keys: ["_now:date", "_now:datetime", "_loaded:date", "_loaded:_datetime"], | ||||
|  | @ -361,49 +411,24 @@ export default class SimpleMetaTagger { | |||
|             return true; | ||||
|         } | ||||
|     ) | ||||
|     public static metatags = [ | ||||
|         SimpleMetaTagger.latlon, | ||||
|         SimpleMetaTagger.layerInfo, | ||||
|         SimpleMetaTagger.surfaceArea, | ||||
|         SimpleMetaTagger.lngth, | ||||
|         SimpleMetaTagger.canonicalize, | ||||
|         SimpleMetaTagger.country, | ||||
|         SimpleMetaTagger.isOpen, | ||||
|         SimpleMetaTagger.directionSimplified, | ||||
|         SimpleMetaTagger.currentTime, | ||||
|         SimpleMetaTagger.objectMetaInfo, | ||||
|         SimpleMetaTagger.noBothButLeftRight | ||||
|     public static metatags: SimpleMetaTagger[] = [ | ||||
|         SimpleMetaTaggers.latlon, | ||||
|         SimpleMetaTaggers.layerInfo, | ||||
|         SimpleMetaTaggers.surfaceArea, | ||||
|         SimpleMetaTaggers.lngth, | ||||
|         SimpleMetaTaggers.canonicalize, | ||||
|         SimpleMetaTaggers.country, | ||||
|         SimpleMetaTaggers.isOpen, | ||||
|         SimpleMetaTaggers.directionSimplified, | ||||
|         SimpleMetaTaggers.currentTime, | ||||
|         SimpleMetaTaggers.objectMetaInfo, | ||||
|         SimpleMetaTaggers.noBothButLeftRight | ||||
| 
 | ||||
|     ]; | ||||
|     public readonly keys: string[]; | ||||
|     public readonly doc: string; | ||||
|     public readonly isLazy: boolean; | ||||
|     public readonly includesDates: boolean | ||||
|     public readonly applyMetaTagsOnFeature: (feature: any, freshness: Date, layer: LayerConfig) => boolean; | ||||
| 
 | ||||
|     public static readonly lazyTags: string[] = [].concat(...SimpleMetaTagger.metatags.filter(tagger => tagger.isLazy) | ||||
|     public static readonly lazyTags: string[] = [].concat(...SimpleMetaTaggers.metatags.filter(tagger => tagger.isLazy) | ||||
|         .map(tagger => tagger.keys)); | ||||
|      | ||||
|     /*** | ||||
|      * A function that adds some extra data to a feature | ||||
|      * @param docs: what does this extra data do? | ||||
|      * @param f: apply the changes. Returns true if something changed | ||||
|      */ | ||||
|     constructor(docs: { keys: string[], doc: string, includesDates?: boolean, isLazy?: boolean, cleanupRetagger?: boolean }, | ||||
|                 f: ((feature: any, freshness: Date, layer: LayerConfig) => boolean)) { | ||||
|         this.keys = docs.keys; | ||||
|         this.doc = docs.doc; | ||||
|         this.isLazy = docs.isLazy | ||||
|         this.applyMetaTagsOnFeature = f; | ||||
|         this.includesDates = docs.includesDates ?? false; | ||||
|         if (!docs.cleanupRetagger) { | ||||
|             for (const key of docs.keys) { | ||||
|                 if (!key.startsWith('_') && key.toLowerCase().indexOf("theme") < 0) { | ||||
|                     throw `Incorrect metakey ${key}: it should start with underscore (_)` | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Edits the given object to rewrite 'both'-tagging into a 'left-right' tagging scheme. | ||||
|  | @ -494,7 +519,7 @@ export default class SimpleMetaTagger { | |||
| 
 | ||||
|         subElements.push(new Title("Metatags calculated by MapComplete", 2)) | ||||
|         subElements.push(new FixedUiElement("The following values are always calculated, by default, by MapComplete and are available automatically on all elements in every theme")) | ||||
|         for (const metatag of SimpleMetaTagger.metatags) { | ||||
|         for (const metatag of SimpleMetaTaggers.metatags) { | ||||
|             subElements.push( | ||||
|                 new Title(metatag.keys.join(", "), 3), | ||||
|                 metatag.doc, | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ | |||
|     { | ||||
|       "id": "render_crab", | ||||
|       "render": { | ||||
|         "nl": "Volgens het CRAB ligt hier <b>{STRAATNM}</b> {HUISNR} (label: {_HNRLABEL})" | ||||
|         "nl": "Volgens het CRAB ligt hier <b>{STRAATNM}</b> {HUISNR} (label: {HNRLABEL})" | ||||
|       } | ||||
|     } | ||||
|   ] | ||||
|  |  | |||
|  | @ -370,8 +370,8 @@ | |||
|                         "_embedding_street!:={STRAATNM}" | ||||
|                       ] | ||||
|                     }, | ||||
|                     {    "#": "Matches the embedding GRB object", | ||||
| 
 | ||||
|                     { | ||||
|                       "#": "Matches the embedding GRB object", | ||||
|                       "or": [ | ||||
|                         "_embedding_nr_grb!:={HUISNR}", | ||||
|                         "_embedding_street_grb!:={STRAATNM}" | ||||
|  | @ -453,15 +453,14 @@ | |||
|           "render": "{import_button(OSM-buildings,building=$building;man_made=$man_made; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber; building:min_level=$_building:min_level, Upload this building to OpenStreetMap)}", | ||||
|           "mappings": [ | ||||
|             { | ||||
|               "if": {"and": | ||||
|               [ | ||||
|                 "_overlaps_with!=", | ||||
|                 "_osm_obj:addr:street=", | ||||
|                 "_osm_obj:addr:housenumber=", | ||||
|                 "addr:street~*", | ||||
|                 "addr:housenumber~*" | ||||
| 
 | ||||
|               ] | ||||
|               "if": { | ||||
|                 "and": [ | ||||
|                   "_overlaps_with!=", | ||||
|                   "_osm_obj:addr:street=", | ||||
|                   "_osm_obj:addr:housenumber=", | ||||
|                   "addr:street~*", | ||||
|                   "addr:housenumber~*" | ||||
|                 ] | ||||
|               }, | ||||
|               "then": "{import_button(OSM-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber, Replace the geometry in OpenStreetMap and add the address,,,_osm_obj:id)}" | ||||
|             }, | ||||
|  | @ -578,21 +577,22 @@ | |||
|             "centroid" | ||||
|           ] | ||||
|         }, | ||||
|         { "width": { | ||||
|           "render": 5, | ||||
|           "mappings": [ | ||||
|             { | ||||
|               "if": "_imported=yes", | ||||
|               "then": "1" | ||||
|             } | ||||
|           ] | ||||
|         }, | ||||
|         { | ||||
|           "width": { | ||||
|             "render": 5, | ||||
|             "mappings": [ | ||||
|               { | ||||
|                 "if": "_imported=yes", | ||||
|                 "then": "1" | ||||
|               } | ||||
|             ] | ||||
|           }, | ||||
|           "color": { | ||||
|             "render": "#00a", | ||||
|             "mappings": [ | ||||
|               { | ||||
|                 "if": "_imported=yes", | ||||
|                 "then":"#00ff00" | ||||
|                 "then": "#00ff00" | ||||
|               }, | ||||
|               { | ||||
|                 "if": { | ||||
|  |  | |||
							
								
								
									
										146
									
								
								assets/themes/postal_codes.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								assets/themes/postal_codes.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,146 @@ | |||
| { | ||||
|   "id": "postal_codes", | ||||
|   "title": { | ||||
|     "en": "Postal codes" | ||||
|   }, | ||||
|   "shortDescription": { | ||||
|     "en": "Postal codes" | ||||
|   }, | ||||
|   "description": { | ||||
|     "en": "Postal codes" | ||||
|   }, | ||||
|   "language": [ | ||||
|     "en" | ||||
|   ], | ||||
|   "maintainer": "", | ||||
|   "icon": "./assets/svg/bug.svg", | ||||
|   "version": "0", | ||||
|   "startLat": 0, | ||||
|   "startLon": 0, | ||||
|   "startZoom": 1, | ||||
|   "widenFactor": 0.05, | ||||
|   "socialImage": "", | ||||
|   "layers": [ | ||||
|     { | ||||
|       "id": "postal_codes", | ||||
|       "name": { | ||||
|         "en": "postal codes" | ||||
|       }, | ||||
|       "minzoom": 12, | ||||
|       "title": { | ||||
|         "render": { | ||||
|           "en": "Postal code {postal_code}" | ||||
|         } | ||||
|       }, | ||||
|       "description": {}, | ||||
|       "tagRenderings": [ | ||||
|         { | ||||
|           "id": "postal_code", | ||||
|           "render": { | ||||
|             "en": "The postal code is {postal_code}" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "presets": [], | ||||
|       "source": { | ||||
|         "isOsmCache": true, | ||||
|         "geoJson": "http://127.0.0.1:8080/postal_codes_postal_codes_{z}_{x}_{y}.geojson", | ||||
|         "geoJsonZoomLevel": 1, | ||||
|         "osmTags": { | ||||
|           "or": [ | ||||
|             "boundary=postal_code", | ||||
|             { | ||||
|               "and": [ | ||||
|                 "bounary=administrative", | ||||
|                 "postal_code~*" | ||||
|               ] | ||||
|             } | ||||
|           ] | ||||
|         } | ||||
|       }, | ||||
|       "mapRendering": [ | ||||
|         { | ||||
|           "icon": { | ||||
|             "render": "./assets/svg/bug.svg" | ||||
|           }, | ||||
|           "iconSize": { | ||||
|             "render": "40,40,center" | ||||
|           }, | ||||
|           "location": [ | ||||
|             "point", | ||||
|             "centroid" | ||||
|           ] | ||||
|         }, | ||||
|         { | ||||
|           "color": { | ||||
|             "render": "#00f" | ||||
|           }, | ||||
|           "width": { | ||||
|             "render": "8" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "isShown": { | ||||
|         "render": "yes", | ||||
|         "mappings": [{ | ||||
|           "if" :"_country!=be", | ||||
|           "then": "no" | ||||
|         }] | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "id": "town_halls", | ||||
|       "name": { | ||||
|         "en": "town halls" | ||||
|       }, | ||||
|       "minzoom": 12, | ||||
|       "title": { | ||||
|         "render": { | ||||
|           "en": "Town halls" | ||||
|         } | ||||
|       }, | ||||
|       "calculatedTags": [ | ||||
|         "_postal_code=feat.overlapWith('postal_codes')[0]?.feat?.properties?.postal_code" | ||||
|       ], | ||||
|       "description": {}, | ||||
|       "tagRenderings": [ | ||||
|       ], | ||||
|       "presets": [], | ||||
|       "source": { | ||||
|         "isOsmCache": true, | ||||
|         "geoJson": "http://127.0.0.1:8080/postal_codes_town_hall_{z}_{x}_{y}.geojson", | ||||
|         "geoJsonZoomLevel": 1, | ||||
|         "osmTags": "amenity=townhall" | ||||
|       }, | ||||
|       "mapRendering": [ | ||||
|         { | ||||
|           "icon": { | ||||
|             "render": "./assets/svg/bug.svg" | ||||
|           }, | ||||
|           "iconSize": { | ||||
|             "render": "40,40,center" | ||||
|           }, | ||||
|           "location": [ | ||||
|             "point", | ||||
|             "centroid" | ||||
|           ] | ||||
|         }, | ||||
|         { | ||||
|           "color": { | ||||
|             "render": "#00f" | ||||
|           }, | ||||
|           "width": { | ||||
|             "render": "8" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "isShown": { | ||||
|         "render": "yes", | ||||
|         "mappings": [{ | ||||
|           "if" :"_country!=be", | ||||
|           "then": "no" | ||||
|         }] | ||||
|       } | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										4
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										4
									
								
								index.ts
									
										
									
									
									
								
							|  | @ -3,8 +3,6 @@ import {QueryParameters} from "./Logic/Web/QueryParameters"; | |||
| import Combine from "./UI/Base/Combine"; | ||||
| import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; | ||||
| import MinimapImplementation from "./UI/Base/MinimapImplementation"; | ||||
| import CountryCoder from "latlon2country/index"; | ||||
| import SimpleMetaTagger from "./Logic/SimpleMetaTagger"; | ||||
| import {Utils} from "./Utils"; | ||||
| import AllThemesGui from "./UI/AllThemesGui"; | ||||
| import DetermineLayout from "./Logic/DetermineLayout"; | ||||
|  | @ -14,12 +12,10 @@ import State from "./State"; | |||
| import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayersImplementation"; | ||||
| import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation"; | ||||
| import {DefaultGuiState} from "./UI/DefaultGuiState"; | ||||
| import {Browser} from "leaflet"; | ||||
| 
 | ||||
| // Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts running from console
 | ||||
| MinimapImplementation.initialize() | ||||
| AvailableBaseLayers.implement(new AvailableBaseLayersImplementation()) | ||||
| SimpleMetaTagger.coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/"); | ||||
| ShowOverlayLayerImplementation.Implement(); | ||||
| // Miscelleanous
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1032,6 +1032,24 @@ | |||
|         "shortDescription": "A map with playgrounds", | ||||
|         "title": "Playgrounds" | ||||
|     }, | ||||
|     "postal_codes": { | ||||
|         "description": "Postal codes", | ||||
|         "layers": { | ||||
|             "0": { | ||||
|                 "name": "postal codes", | ||||
|                 "tagRenderings": { | ||||
|                     "postal_code": { | ||||
|                         "render": "The postal code is {postal_code}" | ||||
|                     } | ||||
|                 }, | ||||
|                 "title": { | ||||
|                     "render": "Postal code {postal_code}" | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "shortDescription": "Postal codes", | ||||
|         "title": "Postal codes" | ||||
|     }, | ||||
|     "postboxes": { | ||||
|         "description": "On this map you can find and add data of post offices and post boxes. You can use this map to find where you can mail your next postcard! :)<br/>Spotted an error or is a post box missing? You can edit this map with a free OpenStreetMap account. ", | ||||
|         "layers": { | ||||
|  |  | |||
							
								
								
									
										4215
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										4215
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -77,7 +77,7 @@ | |||
|     "idb-keyval": "^6.0.3", | ||||
|     "jquery": "^3.6.0", | ||||
|     "jspdf": "^2.3.1", | ||||
|     "latlon2country": "^1.1.3", | ||||
|     "latlon2country": "^1.2.3", | ||||
|     "leaflet": "^1.7.1", | ||||
|     "leaflet-polylineoffset": "^1.1.1", | ||||
|     "leaflet-providers": "^1.13.0", | ||||
|  |  | |||
|  | @ -21,7 +21,9 @@ import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSou | |||
| import TiledFeatureSource from "../Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource"; | ||||
| import Constants from "../Models/Constants"; | ||||
| import {GeoOperations} from "../Logic/GeoOperations"; | ||||
| 
 | ||||
| import SimpleMetaTaggers from "../Logic/SimpleMetaTagger"; | ||||
| import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource"; | ||||
| import Loc from "../Models/Loc"; | ||||
| 
 | ||||
| ScriptUtils.fixUtils() | ||||
| 
 | ||||
|  | @ -177,12 +179,15 @@ function loadAllTiles(targetdir: string, r: TileRange, theme: LayoutConfig, extr | |||
|  * Load all the tiles into memory from disk | ||||
|  */ | ||||
| function sliceToTiles(allFeatures: FeatureSource, theme: LayoutConfig, relationsTracker: RelationsTracker, targetdir: string, pointsOnlyLayers: string[]) { | ||||
|     function handleLayer(source: FeatureSourceForLayer) { | ||||
|     const skippedLayers = new Set<string>() | ||||
| 
 | ||||
|     async function handleLayer(source: FeatureSourceForLayer) { | ||||
|         const layer = source.layer.layerDef; | ||||
|         const targetZoomLevel = layer.source.geojsonZoomLevel ?? 0 | ||||
| 
 | ||||
|         const layerId = layer.id | ||||
|         if (layer.source.isOsmCacheLayer !== true) { | ||||
|             skippedLayers.add(layer.id) | ||||
|             return; | ||||
|         } | ||||
|         console.log("Handling layer ", layerId, "which has", source.features.data.length, "features") | ||||
|  | @ -201,6 +206,13 @@ function sliceToTiles(allFeatures: FeatureSource, theme: LayoutConfig, relations | |||
|                 includeDates: false, | ||||
|                 includeNonDates: true | ||||
|             }); | ||||
|          | ||||
|          | ||||
| 
 | ||||
|         while (SimpleMetaTaggers.country.runningTasks.size > 0) { | ||||
|             console.log("Still waiting for ", SimpleMetaTaggers.country.runningTasks.size," features which don't have a country yet") | ||||
|             await ScriptUtils.sleep(1) | ||||
|         } | ||||
| 
 | ||||
|         const createdTiles = [] | ||||
|         // At this point, we have all the features of the entire area.
 | ||||
|  | @ -210,23 +222,57 @@ function sliceToTiles(allFeatures: FeatureSource, theme: LayoutConfig, relations | |||
|             maxZoomLevel: targetZoomLevel, | ||||
|             maxFeatureCount: undefined, | ||||
|             registerTile: tile => { | ||||
|                 const tileIndex = tile.tileIndex; | ||||
|                 if (tile.features.data.length === 0) { | ||||
|                     return | ||||
|                 } | ||||
|                 for (const feature of tile.features.data) { | ||||
|                 | ||||
|                 const filteredTile = new FilteringFeatureSource({ | ||||
|                   locationControl:  new UIEventSource<Loc>(undefined), | ||||
|                     allElements: undefined, | ||||
|                     selectedElement: new UIEventSource<any>(undefined) | ||||
|                 }, | ||||
|                     tileIndex, | ||||
|                     tile, | ||||
|                     new UIEventSource<any>(undefined) | ||||
|                     ) | ||||
| 
 | ||||
|                 if (filteredTile.features.data.length === 0) { | ||||
|                     return | ||||
|                 } | ||||
|                 let strictlyCalculated = 0 | ||||
|                 let featureCount = 0 | ||||
|                 for (const feature of filteredTile.features.data) { | ||||
|                     // Some cleanup
 | ||||
|                     delete feature.feature["bbox"] | ||||
|                      | ||||
|                     if(tile.layer.layerDef.calculatedTags !== undefined){ | ||||
|                          | ||||
|                     // Evaluate all the calculated tags strictly
 | ||||
|                     const calculatedTagKeys = tile.layer.layerDef.calculatedTags.map(ct => ct[0]) | ||||
|                     featureCount++ | ||||
|                     for (const calculatedTagKey of calculatedTagKeys) { | ||||
|                         const strict =  feature.feature.properties[calculatedTagKey] | ||||
|                         feature.feature.properties[calculatedTagKey] =strict | ||||
|                         strictlyCalculated ++; | ||||
|                         if(strictlyCalculated % 100 === 0){ | ||||
|                             console.log("Strictly calculated ", strictlyCalculated, "values for tile",tileIndex,": now at ", featureCount,"/",filteredTile.features.data.length, "examle value: ", strict) | ||||
|                         } | ||||
|                     } | ||||
|                     } | ||||
|                      | ||||
|                 } | ||||
|                 // Lets save this tile!
 | ||||
|                 const [z, x, y] = Tiles.tile_from_index(tile.tileIndex) | ||||
|                 const [z, x, y] = Tiles.tile_from_index(tileIndex) | ||||
|                 // console.log("Writing tile ", z, x, y, layerId)
 | ||||
|                 const targetPath = geoJsonName(targetdir + "_" + layerId, x, y, z) | ||||
|                 createdTiles.push(tile.tileIndex) | ||||
|                 createdTiles.push(tileIndex) | ||||
|                 // This is the geojson file containing all features for this tile
 | ||||
|                 writeFileSync(targetPath, JSON.stringify({ | ||||
|                     type: "FeatureCollection", | ||||
|                     features: tile.features.data.map(f => f.feature) | ||||
|                     features: filteredTile.features.data.map(f => f.feature) | ||||
|                 }, null, " ")) | ||||
|                 console.log("Written tile", targetPath,"with", filteredTile.features.data.length) | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|  | @ -267,18 +313,31 @@ function sliceToTiles(allFeatures: FeatureSource, theme: LayoutConfig, relations | |||
|         handleLayer, | ||||
|         allFeatures | ||||
|     ) | ||||
| 
 | ||||
|     const skipped = Array.from(skippedLayers) | ||||
|     if (skipped.length > 0) { | ||||
|         console.warn("Did not save any cache files for layers " + skipped.join(", ") + " as these didn't set the flag `isOsmCache` to true") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| async function main(args: string[]) { | ||||
| 
 | ||||
|     if (args.length == 0) { | ||||
|         console.error("Expected arguments are: theme zoomlevel targetdirectory lat0 lon0 lat1 lon1 [--generate-point-overview layer-name,layer-name,...]") | ||||
|     console.log("Cache builder started with args ", args.join(", ")) | ||||
|     if (args.length < 6) { | ||||
|         console.error("Expected arguments are: theme zoomlevel targetdirectory lat0 lon0 lat1 lon1 [--generate-point-overview layer-name,layer-name,...]\n" + | ||||
|             "Note: a new directory named <theme> will be created in targetdirectory") | ||||
|         return; | ||||
|     } | ||||
|     const themeName = args[0] | ||||
|     const zoomlevel = Number(args[1]) | ||||
| 
 | ||||
|     const targetdir = args[2] + "/" + themeName | ||||
|     if (!existsSync(args[2])) { | ||||
|         console.log("Directory not found") | ||||
|         throw "The directory " + args[2] + "does not exist" | ||||
|     } | ||||
| 
 | ||||
|     const lat0 = Number(args[3]) | ||||
|     const lon0 = Number(args[4]) | ||||
|     const lat1 = Number(args[5]) | ||||
|  | @ -292,6 +351,11 @@ async function main(args: string[]) { | |||
| 
 | ||||
|     const tileRange = Tiles.TileRangeBetween(zoomlevel, lat0, lon0, lat1, lon1) | ||||
| 
 | ||||
|     if (tileRange.total === 0) { | ||||
|         console.log("Tilerange has zero tiles - this is probably an error") | ||||
|         return | ||||
|     } | ||||
| 
 | ||||
|     const theme = AllKnownLayouts.allKnownLayouts.get(themeName) | ||||
|     if (theme === undefined) { | ||||
|         const keys = [] | ||||
|  | @ -321,5 +385,9 @@ async function main(args: string[]) { | |||
| 
 | ||||
| let args = [...process.argv] | ||||
| args.splice(0, 2) | ||||
| main(args); | ||||
| try { | ||||
|     main(args).catch(e => console.error("Error building cache:", e)); | ||||
| } catch (e) { | ||||
|     console.error("Error building cache:", e) | ||||
| } | ||||
| console.log("All done!") | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue