forked from MapComplete/MapComplete
		
	Merge develop
This commit is contained in:
		
						commit
						b4b72a0995
					
				
					 15 changed files with 336 additions and 249 deletions
				
			
		|  | @ -35,9 +35,20 @@ export default class SelectedElementTagsUpdater { | |||
| 
 | ||||
| 
 | ||||
|         state.selectedElement.addCallbackAndRunD(s => { | ||||
|             const id = s.properties?.id | ||||
|             OsmObject.DownloadObjectAsync(id).then(obj => { | ||||
|                 SelectedElementTagsUpdater.applyUpdate(state, obj, id) | ||||
|             let id = s.properties?.id | ||||
|              | ||||
|             const backendUrl = state.osmConnection._oauth_config.url | ||||
|             if(id.startsWith(backendUrl)){ | ||||
|                 id = id.substring(backendUrl.length) | ||||
|             } | ||||
|              | ||||
|             if(!(id.startsWith("way") || id.startsWith("node") || id.startsWith("relation"))){ | ||||
|                 // This object is _not_ from OSM, so we skip it!
 | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             OsmObject.DownloadPropertiesOf(id).then(tags => { | ||||
|                 SelectedElementTagsUpdater.applyUpdate(state, tags, id) | ||||
|             }).catch(e => { | ||||
|                 console.error("Could not update tags of ", id, "due to", e) | ||||
|             }) | ||||
|  | @ -50,14 +61,12 @@ export default class SelectedElementTagsUpdater { | |||
|                                    allElements: ElementStorage, | ||||
|                                    changes: Changes, | ||||
|                                    osmConnection: OsmConnection | ||||
|                                }, obj: OsmObject, id: string | ||||
|                                }, latestTags: any, id: string | ||||
|     ) { | ||||
|         const pendingChanges = state.changes.pendingChanges.data | ||||
|             .filter(change => change.type === obj.type && change.id === obj.id) | ||||
|             .filter(change => change.type +"/"+ change.id === id) | ||||
|             .filter(change => change.tags !== undefined); | ||||
|         const latestTags = obj.tags | ||||
|         console.log("Applying updates of ", id, " got tags", latestTags, "and still have to apply changes: ", pendingChanges) | ||||
| 
 | ||||
|         | ||||
|         for (const pendingChange of pendingChanges) { | ||||
|             const tagChanges = pendingChange.tags; | ||||
|             for (const tagChange of tagChanges) { | ||||
|  | @ -84,7 +93,7 @@ export default class SelectedElementTagsUpdater { | |||
|              | ||||
|             const localValue = currentTags[key] | ||||
|             if (localValue !== osmValue) { | ||||
|                 console.log("Local value:", localValue, "upstream", osmValue) | ||||
|                 console.log("Local value for ", key ,":", localValue, "upstream", osmValue) | ||||
|                 somethingChanged = true; | ||||
|                 currentTags[key] = osmValue | ||||
|             } | ||||
|  | @ -92,6 +101,8 @@ export default class SelectedElementTagsUpdater { | |||
|         if (somethingChanged) { | ||||
|             console.log("Detected upstream changes to the object when opening it, updating...") | ||||
|             currentTagsSource.ping() | ||||
|         }else{ | ||||
|             console.debug("Fetched latest tags for ", id, "but detected no changes") | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
|  |  | |||
|  | @ -135,10 +135,10 @@ export class ExtraFunction { | |||
|             args: ["list of features or layer name", "amount of features", "unique tag key (optional)", "maxDistanceInMeters (optional)"] | ||||
|         }, | ||||
|         (params, feature) => { | ||||
|             | ||||
| 
 | ||||
|             return (features, amount, uniqueTag, maxDistanceInMeters) => { | ||||
|                 let distance : number = Number(maxDistanceInMeters) | ||||
|                 if(isNaN(distance)){ | ||||
|                 let distance: number = Number(maxDistanceInMeters) | ||||
|                 if (isNaN(distance)) { | ||||
|                     distance = undefined | ||||
|                 } | ||||
|                 return ExtraFunction.GetClosestNFeatures(params, feature, features, { | ||||
|  | @ -164,32 +164,13 @@ export class ExtraFunction { | |||
| 
 | ||||
|         } | ||||
|     ) | ||||
|     private static readonly AspectedRouting = new ExtraFunction( | ||||
|         { | ||||
|             name: "score", | ||||
|             doc: "Given the path of an aspected routing json file, will calculate the score. This score is wrapped in a UIEventSource, so for further calculations, use `.map(score => ...)`" + | ||||
|                 "\n\n" + | ||||
|                 "For example: `_comfort_score=feat.score('https://raw.githubusercontent.com/pietervdvn/AspectedRouting/master/Examples/bicycle/aspects/bicycle.comfort.json')`", | ||||
|             args: ["path"] | ||||
|         }, | ||||
|         (_, feature) => { | ||||
|             return (path) => { | ||||
|                 return UIEventSourceTools.downloadJsonCached(path).map(config => { | ||||
|                     if (config === undefined) { | ||||
|                         return | ||||
|                     } | ||||
|                     return new AspectedRouting(config).evaluate(feature.properties) | ||||
|                 }) | ||||
|             } | ||||
|         } | ||||
|     ) | ||||
| 
 | ||||
|     private static readonly allFuncs: ExtraFunction[] = [ | ||||
|         ExtraFunction.DistanceToFunc, | ||||
|         ExtraFunction.OverlapFunc, | ||||
|         ExtraFunction.ClosestObjectFunc, | ||||
|         ExtraFunction.ClosestNObjectFunc, | ||||
|         ExtraFunction.Memberships, | ||||
|         ExtraFunction.AspectedRouting | ||||
|         ExtraFunction.Memberships | ||||
|     ]; | ||||
|     private readonly _name: string; | ||||
|     private readonly _args: string[]; | ||||
|  | @ -243,33 +224,30 @@ export class ExtraFunction { | |||
|         const maxFeatures = options?.maxFeatures ?? 1 | ||||
|         const maxDistance = options?.maxDistance ?? 500 | ||||
|         const uniqueTag: string | undefined = options?.uniqueTag | ||||
|         console.log("Requested closestN") | ||||
|         if (typeof features === "string") { | ||||
|             const name = features | ||||
|             const bbox = GeoOperations.bbox(GeoOperations.buffer(GeoOperations.bbox(feature), maxDistance)) | ||||
|             features = params.getFeaturesWithin(name, new BBox(bbox.geometry.coordinates)) | ||||
|         }else{ | ||||
|         } else { | ||||
|             features = [features] | ||||
|         } | ||||
|         if (features === undefined) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const selfCenter = GeoOperations.centerpointCoordinates(feature) | ||||
|         let closestFeatures: { feat: any, distance: number }[] = []; | ||||
|         for(const featureList of features) { | ||||
|         for (const featureList of features) { | ||||
|             for (const otherFeature of featureList) { | ||||
|                 if (otherFeature === feature || otherFeature.id === feature.id) { | ||||
|                     continue; // We ignore self
 | ||||
|                 } | ||||
|                 let distance = undefined; | ||||
|                 if (otherFeature._lon !== undefined && otherFeature._lat !== undefined) { | ||||
|                     distance = GeoOperations.distanceBetween([otherFeature._lon, otherFeature._lat], [feature._lon, feature._lat]); | ||||
|                 } else { | ||||
|                     distance = GeoOperations.distanceBetween( | ||||
|                         GeoOperations.centerpointCoordinates(otherFeature), | ||||
|                         [feature._lon, feature._lat] | ||||
|                     ) | ||||
|                 } | ||||
|                 if (distance === undefined || distance === null) { | ||||
|                 const distance = GeoOperations.distanceBetween( | ||||
|                     GeoOperations.centerpointCoordinates(otherFeature), | ||||
|                     selfCenter | ||||
|                 ) | ||||
|                 if (distance === undefined || distance === null || isNaN(distance)) { | ||||
|                     console.error("Could not calculate the distance between", feature, "and", otherFeature) | ||||
|                     throw "Undefined distance!" | ||||
|                 } | ||||
|  |  | |||
|  | @ -4,12 +4,14 @@ | |||
|  * Technically, more an Actor then a featuresource, but it fits more neatly this ay | ||||
|  */ | ||||
| import {FeatureSourceForLayer} from "../FeatureSource"; | ||||
| import SimpleMetaTagger from "../../SimpleMetaTagger"; | ||||
| 
 | ||||
| export default class SaveTileToLocalStorageActor { | ||||
|     public static readonly storageKey: string = "cached-features"; | ||||
|     public static readonly formatVersion: string = "1" | ||||
| 
 | ||||
|     constructor(source: FeatureSourceForLayer, tileIndex: number) { | ||||
|          | ||||
|         source.features.addCallbackAndRunD(features => { | ||||
|             const key = `${SaveTileToLocalStorageActor.storageKey}-${source.layer.layerDef.id}-${tileIndex}` | ||||
|             const now = new Date() | ||||
|  |  | |||
|  | @ -243,7 +243,7 @@ export default class FeaturePipeline { | |||
|         this.runningQuery = updater.runningQuery.map( | ||||
|             overpass => { | ||||
|                 console.log("FeaturePipeline: runningQuery state changed. Overpass", overpass ? "is querying," : "is idle,", | ||||
|                     "osmFeatureSource is", osmFeatureSource.isRunning ? "is running ("+  +")" : "is idle") | ||||
|                     "osmFeatureSource is", osmFeatureSource.isRunning ? "is running and needs "+neededTilesFromOsm.data?.length+" tiles (already got "+ osmFeatureSource.downloadedTiles.size  +" tiles )" : "is idle") | ||||
|                 return overpass || osmFeatureSource.isRunning.data; | ||||
|             }, [osmFeatureSource.isRunning] | ||||
|         ) | ||||
|  | @ -361,7 +361,6 @@ export default class FeaturePipeline { | |||
|         const self = this | ||||
|         window.setTimeout( | ||||
|             () => { | ||||
|                 console.debug("Applying metatagging onto ", src.name) | ||||
|                 const layerDef = src.layer.layerDef; | ||||
|                 MetaTagging.addMetatags( | ||||
|                     src.features.data, | ||||
|  | @ -384,7 +383,6 @@ export default class FeaturePipeline { | |||
| 
 | ||||
|     private updateAllMetaTagging() { | ||||
|         const self = this; | ||||
|         console.log("Reupdating all metatagging") | ||||
|         this.perLayerHierarchy.forEach(hierarchy => { | ||||
|             hierarchy.loadedTiles.forEach(src => { | ||||
|                 self.applyMetaTags(src) | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ export default class OsmFeatureSource { | |||
|         }, | ||||
|         markTileVisited?: (tileId: number) => void | ||||
|     }; | ||||
|     private readonly downloadedTiles = new Set<number>() | ||||
|     public readonly downloadedTiles = new Set<number>() | ||||
|     private readonly allowedTags: TagsFilter; | ||||
| 
 | ||||
|     constructor(options: { | ||||
|  | @ -52,14 +52,17 @@ export default class OsmFeatureSource { | |||
|             if (options.isActive?.data === false) { | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             neededTiles = neededTiles.filter(tile => !self.downloadedTiles.has(tile)) | ||||
| 
 | ||||
|             if(neededTiles.length == 0){ | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             self.isRunning.setData(true) | ||||
|             try { | ||||
| 
 | ||||
|                 for (const neededTile of neededTiles) { | ||||
|                     if (self.downloadedTiles.has(neededTile)) { | ||||
|                         continue; | ||||
|                     } | ||||
|                     console.log("Tile download", Tiles.tile_from_index(neededTile).join("/"), "started") | ||||
|                     self.downloadedTiles.add(neededTile) | ||||
|                     self.LoadTile(...Tiles.tile_from_index(neededTile)).then(_ => { | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| import SimpleMetaTagger from "./SimpleMetaTagger"; | ||||
| import {ExtraFuncParams, ExtraFunction} from "./ExtraFunction"; | ||||
| import {UIEventSource} from "./UIEventSource"; | ||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||
| import State from "../State"; | ||||
| 
 | ||||
|  | @ -33,34 +32,43 @@ export default class MetaTagging { | |||
|         } | ||||
| 
 | ||||
| 
 | ||||
|             const metatagsToApply: SimpleMetaTagger [] = [] | ||||
|             for (const metatag of SimpleMetaTagger.metatags) { | ||||
|                 if (metatag.includesDates) { | ||||
|                     if (options.includeDates ?? true) { | ||||
|                         metatagsToApply.push(metatag) | ||||
|                     } | ||||
|                 } else { | ||||
|                     if (options.includeNonDates ?? true) { | ||||
|                         metatagsToApply.push(metatag) | ||||
|                     } | ||||
|         const metatagsToApply: SimpleMetaTagger [] = [] | ||||
|         for (const metatag of SimpleMetaTagger.metatags) { | ||||
|             if (metatag.includesDates) { | ||||
|                 if (options.includeDates ?? true) { | ||||
|                     metatagsToApply.push(metatag) | ||||
|                 } | ||||
|             } else { | ||||
|                 if (options.includeNonDates ?? true) { | ||||
|                     metatagsToApply.push(metatag) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // The calculated functions - per layer - which add the new keys
 | ||||
|         const layerFuncs = this.createRetaggingFunc(layer) | ||||
| 
 | ||||
| 
 | ||||
|         for (let i = 0; i < features.length; i++) { | ||||
|                 const ff = features[i]; | ||||
|                 const feature = ff.feature | ||||
|                 const freshness = ff.freshness | ||||
|                 let somethingChanged = false | ||||
|                 for (const metatag of metatagsToApply) { | ||||
|                     try { | ||||
|                         if(!metatag.keys.some(key => feature.properties[key] === undefined)){ | ||||
|                             // All keys are already defined, we probably already ran this one
 | ||||
|                             continue | ||||
|                         } | ||||
|             const ff = features[i]; | ||||
|             const feature = ff.feature | ||||
|             const freshness = ff.freshness | ||||
|             let somethingChanged = false | ||||
|             for (const metatag of metatagsToApply) { | ||||
|                 try { | ||||
|                     if (!metatag.keys.some(key => feature.properties[key] === undefined)) { | ||||
|                         // All keys are already defined, we probably already ran this one
 | ||||
|                         continue | ||||
|                     } | ||||
|                      | ||||
|                     if(metatag.isLazy){ | ||||
|                         somethingChanged = true; | ||||
|                          | ||||
|                         metatag.applyMetaTagsOnFeature(feature, freshness) | ||||
|                          | ||||
|                     }else{ | ||||
|                          | ||||
|                      | ||||
|                         const newValueAdded = metatag.applyMetaTagsOnFeature(feature, freshness) | ||||
|                         /* Note that the expression: | ||||
|                         * `somethingChanged = newValueAdded || metatag.applyMetaTagsOnFeature(feature, freshness)` | ||||
|  | @ -70,35 +78,30 @@ export default class MetaTagging { | |||
|                         * thus not running an update! | ||||
|                         */ | ||||
|                         somethingChanged = newValueAdded || somethingChanged | ||||
|                     } catch (e) { | ||||
|                         console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack) | ||||
|                     } | ||||
|                 } | ||||
|                  | ||||
|                 if(layerFuncs !== undefined){ | ||||
|                     try { | ||||
|                         layerFuncs(params, feature) | ||||
|                     } catch (e) { | ||||
|                         console.error(e) | ||||
|                     } | ||||
|                     somethingChanged = true | ||||
|                 } | ||||
|                  | ||||
|                 if(somethingChanged){ | ||||
|                     State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping() | ||||
|                 } catch (e) { | ||||
|                     console.error("Could not calculate metatag for ", metatag.keys.join(","), ":", e, e.stack) | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (layerFuncs !== undefined) { | ||||
|                 try { | ||||
|                     layerFuncs(params, feature) | ||||
|                 } catch (e) { | ||||
|                     console.error(e) | ||||
|                 } | ||||
|                 somethingChanged = true | ||||
|             } | ||||
| 
 | ||||
|             if (somethingChanged) { | ||||
|                 State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private static createRetaggingFunc(layer: LayerConfig): | ||||
|         ((params: ExtraFuncParams, feature: any) => void) { | ||||
|         const calculatedTags: [string, string][] = layer.calculatedTags; | ||||
|         if (calculatedTags === undefined) { | ||||
|             return undefined; | ||||
|         } | ||||
| 
 | ||||
|         const functions: ((params: ExtraFuncParams, feature: any) => void)[] = []; | ||||
|     private static createFunctionsForFeature(calculatedTags: [string, string][]): ((feature: any) => void)[] { | ||||
|         const functions: ((feature: any) => void)[] = []; | ||||
|         for (const entry of calculatedTags) { | ||||
|             const key = entry[0] | ||||
|             const code = entry[1]; | ||||
|  | @ -108,59 +111,72 @@ export default class MetaTagging { | |||
| 
 | ||||
|             const func = new Function("feat", "return " + code + ";"); | ||||
| 
 | ||||
|             try { | ||||
|                 const f = (featuresPerLayer, feature: any) => { | ||||
|                     try { | ||||
|                         let result = func(feature); | ||||
|                         if (result instanceof UIEventSource) { | ||||
|                             result.addCallbackAndRunD(d => { | ||||
|                                 if (typeof d !== "string") { | ||||
|                                     // Make sure it is a string!
 | ||||
|                                     d = JSON.stringify(d); | ||||
|                                 } | ||||
|                                 feature.properties[key] = d; | ||||
|                             }) | ||||
|                             result = result.data | ||||
|                         } | ||||
|             const f = (feature: any) => { | ||||
|                 delete feature.properties[key] | ||||
| 
 | ||||
|                         if (result === undefined || result === "") { | ||||
|                             return; | ||||
|                         } | ||||
|                         if (typeof result !== "string") { | ||||
|                             // Make sure it is a string!
 | ||||
|                             result = JSON.stringify(result); | ||||
|                         } | ||||
|                         feature.properties[key] = result; | ||||
|                     } catch (e) { | ||||
|                         if (MetaTagging.errorPrintCount < MetaTagging.stopErrorOutputAt) { | ||||
|                             console.warn("Could not calculate a calculated tag defined by " + code + " due to " + e + ". This is code defined in the theme. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features", e,e.stack) | ||||
|                             MetaTagging.errorPrintCount++; | ||||
|                             if (MetaTagging.errorPrintCount == MetaTagging.stopErrorOutputAt) { | ||||
|                                 console.error("Got ", MetaTagging.stopErrorOutputAt, " errors calculating this metatagging - stopping output now") | ||||
|                 Object.defineProperty(feature.properties, key, { | ||||
|                     configurable: true, | ||||
|                     enumerable: false, // By setting this as not enumerable, the localTileSaver will _not_ calculate this
 | ||||
|                     get: function () { | ||||
|                         try { | ||||
|                             // Lazyness for the win!
 | ||||
|                             let result = func(feature); | ||||
| 
 | ||||
|                             if (result === "") { | ||||
|                                 result === undefined | ||||
|                             } | ||||
|                             if (result !== undefined && typeof result !== "string") { | ||||
|                                 // Make sure it is a string!
 | ||||
|                                 result = JSON.stringify(result); | ||||
|                             } | ||||
|                             delete feature.properties[key] | ||||
|                             feature.properties[key] = result; | ||||
|                             return result; | ||||
|                         } catch (e) { | ||||
|                             if (MetaTagging.errorPrintCount < MetaTagging.stopErrorOutputAt) { | ||||
|                                 console.warn("Could not calculate a calculated tag defined by " + code + " due to " + e + ". This is code defined in the theme. Are you the theme creator? Doublecheck your code. Note that the metatags might not be stable on new features", e, e.stack) | ||||
|                                 MetaTagging.errorPrintCount++; | ||||
|                                 if (MetaTagging.errorPrintCount == MetaTagging.stopErrorOutputAt) { | ||||
|                                     console.error("Got ", MetaTagging.stopErrorOutputAt, " errors calculating this metatagging - stopping output now") | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                          | ||||
|                     }} ) | ||||
| 
 | ||||
|                 } | ||||
|                 functions.push(f) | ||||
|             } catch (e) { | ||||
|                 console.error("Could not create a dynamic function: ", e) | ||||
|             } | ||||
|              | ||||
|              | ||||
|             functions.push(f) | ||||
|         } | ||||
|         return functions; | ||||
|     } | ||||
| 
 | ||||
|     private static createRetaggingFunc(layer: LayerConfig): | ||||
|         ((params: ExtraFuncParams, feature: any) => void) { | ||||
| 
 | ||||
|         const calculatedTags: [string, string][] = layer.calculatedTags; | ||||
|         if (calculatedTags === undefined || calculatedTags.length === 0) { | ||||
|             return undefined; | ||||
|         } | ||||
| 
 | ||||
|         return (params: ExtraFuncParams, feature) => { | ||||
|             const tags = feature.properties | ||||
|             if (tags === undefined) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             ExtraFunction.FullPatchFeature(params, feature); | ||||
|             try { | ||||
|                 const functions = MetaTagging.createFunctionsForFeature(calculatedTags) | ||||
| 
 | ||||
| 
 | ||||
|                 ExtraFunction.FullPatchFeature(params, feature); | ||||
|                 for (const f of functions) { | ||||
|                     f(params, feature); | ||||
|                     f(feature); | ||||
|                 } | ||||
|                  State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping(); | ||||
|                 State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping(); | ||||
|             } catch (e) { | ||||
|                 console.error("While calculating a tag value: ", e) | ||||
|                 console.error("Invalid syntax in calculated tags or some other error: ", e) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -56,6 +56,19 @@ export abstract class OsmObject { | |||
|         OsmObject.objectCache.set(id, src); | ||||
|         return src; | ||||
|     } | ||||
|      | ||||
|     static async DownloadPropertiesOf(id: string): Promise<any> { | ||||
|         const splitted = id.split("/"); | ||||
|         const type = splitted[0]; | ||||
|         const idN = Number(splitted[1]); | ||||
|         if (idN < 0) { | ||||
|             return undefined; | ||||
|         } | ||||
| 
 | ||||
|         const url = `${OsmObject.backendURL}api/0.6/${id}`; | ||||
|         const rawData = await Utils.downloadJson(url) | ||||
|         return rawData.elements[0].tags | ||||
|     } | ||||
| 
 | ||||
|     static async DownloadObjectAsync(id: string): Promise<OsmObject> { | ||||
|         const splitted = id.split("/"); | ||||
|  | @ -65,7 +78,7 @@ export abstract class OsmObject { | |||
|             return undefined; | ||||
|         } | ||||
| 
 | ||||
|         const full = !id.startsWith("way") ? "" : "/full"; | ||||
|         const full = (id.startsWith("way")) ? "/full" : ""; | ||||
|         const url = `${OsmObject.backendURL}api/0.6/${id}${full}`; | ||||
|         const rawData = await Utils.downloadJson(url) | ||||
|         // A full query might contain more then just the requested object (e.g. nodes that are part of a way, where we only want the way)
 | ||||
|  |  | |||
|  | @ -65,13 +65,34 @@ export default class SimpleMetaTagger { | |||
|     private static surfaceArea = new SimpleMetaTagger( | ||||
|         { | ||||
|             keys: ["_surface", "_surface:ha"], | ||||
|             doc: "The surface area of the feature, in square meters and in hectare. Not set on points and ways" | ||||
|             doc: "The surface area of the feature, in square meters and in hectare. Not set on points and ways", | ||||
|             isLazy: true | ||||
|         }, | ||||
|         (feature => { | ||||
|             const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature); | ||||
|             feature.properties["_surface"] = "" + sqMeters; | ||||
|             feature.properties["_surface:ha"] = "" + Math.floor(sqMeters / 1000) / 10; | ||||
|             feature.area = sqMeters; | ||||
|              | ||||
|             Object.defineProperty(feature.properties, "_surface", { | ||||
|                 enumerable: false, | ||||
|                 configurable: true, | ||||
|                 get: () => { | ||||
|                     const sqMeters = ""+ GeoOperations.surfaceAreaInSqMeters(feature); | ||||
|                     delete feature.properties["_surface"] | ||||
|                     feature.properties["_surface"] = sqMeters; | ||||
|                     return sqMeters | ||||
|                 } | ||||
|             }) | ||||
| 
 | ||||
|             Object.defineProperty(feature.properties, "_surface:ha", { | ||||
|                 enumerable: false, | ||||
|                 configurable: true, | ||||
|                 get: () => { | ||||
|                     const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature); | ||||
|                     const sqMetersHa = "" + Math.floor(sqMeters / 1000) / 10; | ||||
|                     delete feature.properties["_surface:ha"] | ||||
|                     feature.properties["_surface:ha"] = sqMetersHa; | ||||
|                     return sqMetersHa | ||||
|                 } | ||||
|             }) | ||||
|              | ||||
|             return true; | ||||
|         }) | ||||
|     ); | ||||
|  | @ -153,7 +174,7 @@ export default class SimpleMetaTagger { | |||
|             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"]; | ||||
|  | @ -173,7 +194,8 @@ export default class SimpleMetaTagger { | |||
|         { | ||||
|             keys: ["_isOpen", "_isOpen:description"], | ||||
|             doc: "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')", | ||||
|             includesDates: true | ||||
|             includesDates: true, | ||||
|             isLazy: true | ||||
|         }, | ||||
|         (feature => { | ||||
|             if (Utils.runningFromConsole) { | ||||
|  | @ -181,64 +203,74 @@ export default class SimpleMetaTagger { | |||
|                 // isOpen is irrelevant
 | ||||
|                 return false | ||||
|             } | ||||
|              | ||||
|             Object.defineProperty(feature.properties, "_isOpen",{ | ||||
|                 enumerable: false, | ||||
|                 configurable: true, | ||||
|                 get: () => { | ||||
|                     delete feature.properties._isOpen | ||||
|                     feature.properties._isOpen = "" | ||||
|                     const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id); | ||||
|                     tagsSource.addCallbackAndRunD(tags => { | ||||
|                         if (tags.opening_hours === undefined || tags._country === undefined) { | ||||
|                             return; | ||||
|                         } | ||||
|                         try { | ||||
|                             const [lon, lat] = GeoOperations.centerpointCoordinates(feature) | ||||
|                             const oh = new opening_hours(tags["opening_hours"], { | ||||
|                                 lat: lat, | ||||
|                                 lon: lon, | ||||
|                                 address: { | ||||
|                                     country_code: tags._country.toLowerCase() | ||||
|                                 } | ||||
|                             }, {tag_key: "opening_hours"}); | ||||
|                             // AUtomatically triggered on the next change
 | ||||
|                             const updateTags = () => { | ||||
|                                 const oldValueIsOpen = tags["_isOpen"]; | ||||
|                                 const oldNextChange = tags["_isOpen:nextTrigger"] ?? 0; | ||||
| 
 | ||||
|             const tagsSource = State.state.allElements.getEventSourceById(feature.properties.id); | ||||
|             tagsSource.addCallbackAndRunD(tags => { | ||||
|                 if (tags.opening_hours === undefined || tags._country === undefined) { | ||||
|                     return; | ||||
|                                 if (oldNextChange > (new Date()).getTime() && | ||||
|                                     tags["_isOpen:oldvalue"] === tags["opening_hours"]) { | ||||
|                                     // Already calculated and should not yet be triggered
 | ||||
|                                     return false; | ||||
|                                 } | ||||
| 
 | ||||
|                                 tags["_isOpen"] = oh.getState() ? "yes" : "no"; | ||||
|                                 const comment = oh.getComment(); | ||||
|                                 if (comment) { | ||||
|                                     tags["_isOpen:description"] = comment; | ||||
|                                 } | ||||
| 
 | ||||
|                                 if (oldValueIsOpen !== tags._isOpen) { | ||||
|                                     tagsSource.ping(); | ||||
|                                 } | ||||
| 
 | ||||
|                                 const nextChange = oh.getNextChange(); | ||||
|                                 if (nextChange !== undefined) { | ||||
|                                     const timeout = nextChange.getTime() - (new Date()).getTime(); | ||||
|                                     tags["_isOpen:nextTrigger"] = nextChange.getTime(); | ||||
|                                     tags["_isOpen:oldvalue"] = tags.opening_hours | ||||
|                                     window.setTimeout( | ||||
|                                         () => { | ||||
|                                             console.log("Updating the _isOpen tag for ", tags.id, ", it's timer expired after", timeout); | ||||
|                                             updateTags(); | ||||
|                                         }, | ||||
|                                         timeout | ||||
|                                     ) | ||||
|                                 } | ||||
|                             } | ||||
|                             updateTags(); | ||||
|                             return true; // Our job is done, lets unregister!
 | ||||
|                         } catch (e) { | ||||
|                             console.warn("Error while parsing opening hours of ", tags.id, e); | ||||
|                             tags["_isOpen"] = "parse_error"; | ||||
|                         } | ||||
| 
 | ||||
|                     }) | ||||
|                     return feature.properties["_isOpen"] | ||||
|                 } | ||||
|                 try { | ||||
| 
 | ||||
|                     const oh = new opening_hours(tags["opening_hours"], { | ||||
|                         lat: tags._lat, | ||||
|                         lon: tags._lon, | ||||
|                         address: { | ||||
|                             country_code: tags._country.toLowerCase() | ||||
|                         } | ||||
|                     }, {tag_key: "opening_hours"}); | ||||
|                     // AUtomatically triggered on the next change
 | ||||
|                     const updateTags = () => { | ||||
|                         const oldValueIsOpen = tags["_isOpen"]; | ||||
|                         const oldNextChange = tags["_isOpen:nextTrigger"] ?? 0; | ||||
| 
 | ||||
|                         if (oldNextChange > (new Date()).getTime() && | ||||
|                             tags["_isOpen:oldvalue"] === tags["opening_hours"]) { | ||||
|                             // Already calculated and should not yet be triggered
 | ||||
|                             return false; | ||||
|                         } | ||||
| 
 | ||||
|                         tags["_isOpen"] = oh.getState() ? "yes" : "no"; | ||||
|                         const comment = oh.getComment(); | ||||
|                         if (comment) { | ||||
|                             tags["_isOpen:description"] = comment; | ||||
|                         } | ||||
| 
 | ||||
|                         if (oldValueIsOpen !== tags._isOpen) { | ||||
|                             tagsSource.ping(); | ||||
|                         } | ||||
| 
 | ||||
|                         const nextChange = oh.getNextChange(); | ||||
|                         if (nextChange !== undefined) { | ||||
|                             const timeout = nextChange.getTime() - (new Date()).getTime(); | ||||
|                             tags["_isOpen:nextTrigger"] = nextChange.getTime(); | ||||
|                             tags["_isOpen:oldvalue"] = tags.opening_hours | ||||
|                             window.setTimeout( | ||||
|                                 () => { | ||||
|                                     console.log("Updating the _isOpen tag for ", tags.id, ", it's timer expired after", timeout); | ||||
|                                     updateTags(); | ||||
|                                 }, | ||||
|                                 timeout | ||||
|                             ) | ||||
|                         } | ||||
|                     } | ||||
|                     updateTags(); | ||||
|                     return true; | ||||
|                 } catch (e) { | ||||
|                     console.warn("Error while parsing opening hours of ", tags.id, e); | ||||
|                     tags["_isOpen"] = "parse_error"; | ||||
|                 } | ||||
| 
 | ||||
|             }) | ||||
| 
 | ||||
|         }) | ||||
|     ) | ||||
|     private static directionSimplified = new SimpleMetaTagger( | ||||
|  | @ -306,20 +338,25 @@ export default class SimpleMetaTagger { | |||
|         SimpleMetaTagger.objectMetaInfo | ||||
| 
 | ||||
|     ]; | ||||
|     public static readonly lazyTags: string[] = [].concat(...SimpleMetaTagger.metatags.filter(tagger => tagger.isLazy) | ||||
|         .map(tagger => tagger.keys)); | ||||
| 
 | ||||
|     public readonly keys: string[]; | ||||
|     public readonly doc: string; | ||||
|     public readonly isLazy: boolean; | ||||
|     public readonly includesDates: boolean | ||||
|     public readonly applyMetaTagsOnFeature: (feature: any, freshness: Date) => 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 }, | ||||
|     constructor(docs: { keys: string[], doc: string, includesDates?: boolean, isLazy?: boolean }, | ||||
|                 f: ((feature: any, freshness: Date) => boolean)) { | ||||
|         this.keys = docs.keys; | ||||
|         this.doc = docs.doc; | ||||
|         this.isLazy = docs.isLazy | ||||
|         this.applyMetaTagsOnFeature = f; | ||||
|         this.includesDates = docs.includesDates ?? false; | ||||
|         for (const key of docs.keys) { | ||||
|  | @ -345,7 +382,8 @@ export default class SimpleMetaTagger { | |||
|         for (const metatag of SimpleMetaTagger.metatags) { | ||||
|             subElements.push( | ||||
|                 new Title(metatag.keys.join(", "), 3), | ||||
|                 metatag.doc | ||||
|                 metatag.doc, | ||||
|                 metatag.isLazy ? "This is a lazy metatag and is only calculated when needed" : "" | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -47,6 +47,11 @@ export class RegexTag extends TagsFilter { | |||
|     } | ||||
| 
 | ||||
|     matchesProperties(tags: any): boolean { | ||||
|         if(typeof this.key === "string"){ | ||||
|             const value = tags[this.key] ?? "" | ||||
|             return RegexTag.doesMatch(value, this.value) != this.invert; | ||||
|         } | ||||
|          | ||||
|         for (const key in tags) { | ||||
|             if (key === undefined) { | ||||
|                 continue; | ||||
|  |  | |||
|  | @ -23,26 +23,14 @@ export class Tag extends TagsFilter { | |||
| 
 | ||||
| 
 | ||||
|     matchesProperties(properties: any): boolean { | ||||
|         for (const propertiesKey in properties) { | ||||
|             if (!properties.hasOwnProperty(propertiesKey)) { | ||||
|                 continue | ||||
|             } | ||||
|             if (this.key === propertiesKey) { | ||||
|                 const value = properties[propertiesKey]; | ||||
|                 if (value === undefined) { | ||||
|                     continue | ||||
|                 } | ||||
|                 return value === this.value; | ||||
|             } | ||||
|         } | ||||
|         // The tag was not found
 | ||||
| 
 | ||||
|         if (this.value === "") { | ||||
|         const foundValue = properties[this.key] | ||||
|         if (foundValue === undefined && (this.value === "" || this.value === undefined)) { | ||||
|             // The tag was not found
 | ||||
|             // and it shouldn't be found!
 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|         return foundValue === this.value; | ||||
|     } | ||||
| 
 | ||||
|     asOverpass(): string[] { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue