forked from MapComplete/MapComplete
		
	Merge develop
This commit is contained in:
		
						commit
						0162d52b68
					
				
					 127 changed files with 6609 additions and 15167 deletions
				
			
		|  | @ -26,7 +26,6 @@ export default class GeoLocationHandler extends VariableUiElement { | |||
|      * @private | ||||
|      */ | ||||
|     private readonly _permission: UIEventSource<string>; | ||||
| 
 | ||||
|     /*** | ||||
|      * The marker on the map, in order to update it | ||||
|      * @private | ||||
|  | @ -46,15 +45,11 @@ export default class GeoLocationHandler extends VariableUiElement { | |||
|      * @private | ||||
|      */ | ||||
|     private readonly _leafletMap: UIEventSource<L.Map>; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * The date when the user requested the geolocation. If we have a location, it'll autozoom to it the first 30 secs | ||||
|      * @private | ||||
|      */ | ||||
|     private _lastUserRequest: Date; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * A small flag on localstorage. If the user previously granted the geolocation, it will be set. | ||||
|      * On firefox, the permissions api is broken (probably fingerprint resistiance) and "granted + don't ask again" doesn't stick between sessions. | ||||
|  | @ -79,6 +74,7 @@ export default class GeoLocationHandler extends VariableUiElement { | |||
|         ); | ||||
|         const isActive = new UIEventSource<boolean>(false); | ||||
|         const isLocked = new UIEventSource<boolean>(false); | ||||
| 
 | ||||
|         super( | ||||
|             hasLocation.map( | ||||
|                 (hasLocationData) => { | ||||
|  | @ -97,7 +93,6 @@ export default class GeoLocationHandler extends VariableUiElement { | |||
|                     return new CenterFlexedElement( | ||||
|                         Img.AsImageElement(icon, "", "width:1.25rem;height:1.25rem") | ||||
|                     ); | ||||
| 
 | ||||
|                 }, | ||||
|                 [isActive, isLocked] | ||||
|             ) | ||||
|  | @ -133,7 +128,6 @@ export default class GeoLocationHandler extends VariableUiElement { | |||
|         }); | ||||
|         this.init(false); | ||||
| 
 | ||||
| 
 | ||||
|         this._currentGPSLocation.addCallback((location) => { | ||||
|             self._previousLocationGrant.setData("granted"); | ||||
| 
 | ||||
|  | @ -173,10 +167,12 @@ export default class GeoLocationHandler extends VariableUiElement { | |||
| 
 | ||||
|     private init(askPermission: boolean) { | ||||
|         const self = this; | ||||
| 
 | ||||
|         if (self._isActive.data) { | ||||
|             self.MoveToCurrentLoction(16); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             navigator?.permissions | ||||
|                 ?.query({name: "geolocation"}) | ||||
|  | @ -193,6 +189,7 @@ export default class GeoLocationHandler extends VariableUiElement { | |||
|         } catch (e) { | ||||
|             console.error(e); | ||||
|         } | ||||
| 
 | ||||
|         if (askPermission) { | ||||
|             self.StartGeolocating(true); | ||||
|         } else if (this._previousLocationGrant.data === "granted") { | ||||
|  | @ -201,7 +198,7 @@ export default class GeoLocationHandler extends VariableUiElement { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private MoveToCurrentLoction(targetZoom = 16) { | ||||
|     private MoveToCurrentLoction(targetZoom?: number) { | ||||
|         const location = this._currentGPSLocation.data; | ||||
|         this._lastUserRequest = undefined; | ||||
| 
 | ||||
|  | @ -256,6 +253,7 @@ export default class GeoLocationHandler extends VariableUiElement { | |||
|             return; | ||||
|         } | ||||
|         self._isActive.setData(true); | ||||
| 
 | ||||
|         navigator.geolocation.watchPosition( | ||||
|             function (position) { | ||||
|                 self._currentGPSLocation.setData({ | ||||
|  | @ -265,6 +263,9 @@ export default class GeoLocationHandler extends VariableUiElement { | |||
|             }, | ||||
|             function () { | ||||
|                 console.warn("Could not get location with navigator.geolocation"); | ||||
|             }, | ||||
|             { | ||||
|                 enableHighAccuracy: true | ||||
|             } | ||||
|         ); | ||||
|     } | ||||
|  |  | |||
|  | @ -6,6 +6,8 @@ import {Utils} from "../Utils"; | |||
| import BaseUIElement from "../UI/BaseUIElement"; | ||||
| import List from "../UI/Base/List"; | ||||
| import Title from "../UI/Base/Title"; | ||||
| import {UIEventSourceTools} from "./UIEventSource"; | ||||
| import AspectedRouting from "./Osm/aspectedRouting"; | ||||
| 
 | ||||
| export class ExtraFunction { | ||||
| 
 | ||||
|  | @ -38,12 +40,14 @@ export class ExtraFunction { | |||
|         ]), | ||||
|         "Some advanced functions are available on **feat** as well:" | ||||
|     ]).SetClass("flex-col").AsMarkdown(); | ||||
|         | ||||
|      | ||||
| 
 | ||||
| 
 | ||||
|     private static readonly OverlapFunc = new ExtraFunction( | ||||
|         "overlapWith", | ||||
|         "Gives a list of features from the specified layer which this feature (partly) overlaps with. If the current feature is a point, all features that embed the point are given. The returned value is `{ feat: GeoJSONFeature, overlap: number}[]` where `overlap` is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or `undefined` if the current feature is a point", | ||||
|         ["...layerIds - one or more layer ids  of the layer from which every feature is checked for overlap)"], | ||||
|         { | ||||
|             name: "overlapWith", | ||||
|             doc: "Gives a list of features from the specified layer which this feature (partly) overlaps with. If the current feature is a point, all features that embed the point are given. The returned value is `{ feat: GeoJSONFeature, overlap: number}[]` where `overlap` is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or `undefined` if the current feature is a point", | ||||
|             args: ["...layerIds - one or more layer ids  of the layer from which every feature is checked for overlap)"] | ||||
|         }, | ||||
|         (params, feat) => { | ||||
|             return (...layerIds: string[]) => { | ||||
|                 const result = [] | ||||
|  | @ -62,9 +66,11 @@ export class ExtraFunction { | |||
|         } | ||||
|     ) | ||||
|     private static readonly DistanceToFunc = new ExtraFunction( | ||||
|         "distanceTo", | ||||
|         "Calculates the distance between the feature and a specified point in kilometer. The input should either be a pair of coordinates, a geojson feature or the ID of an object", | ||||
|         ["longitude", "latitude"], | ||||
|         { | ||||
|             name: "distanceTo", | ||||
|             doc: "Calculates the distance between the feature and a specified point in kilometer. The input should either be a pair of coordinates, a geojson feature or the ID of an object", | ||||
|             args: ["longitude", "latitude"] | ||||
|         }, | ||||
|         (featuresPerLayer, feature) => { | ||||
|             return (arg0, lat) => { | ||||
|                 if (typeof arg0 === "number") { | ||||
|  | @ -88,9 +94,11 @@ export class ExtraFunction { | |||
|     ) | ||||
| 
 | ||||
|     private static readonly ClosestObjectFunc = new ExtraFunction( | ||||
|         "closest", | ||||
|         "Given either a list of geojson features or a single layer name, gives the single object which is nearest to the feature. In the case of ways/polygons, only the centerpoint is considered.", | ||||
|         ["list of features"], | ||||
|         { | ||||
|             name: "closest", | ||||
|             doc: "Given either a list of geojson features or a single layer name, gives the single object which is nearest to the feature. In the case of ways/polygons, only the centerpoint is considered.", | ||||
|             args: ["list of features"] | ||||
|         }, | ||||
|         (params, feature) => { | ||||
|             return (features) => { | ||||
|                 if (typeof features === "string") { | ||||
|  | @ -139,28 +147,56 @@ export class ExtraFunction { | |||
| 
 | ||||
| 
 | ||||
|     private static readonly Memberships = new ExtraFunction( | ||||
|         "memberships", | ||||
|         "Gives a list of `{role: string, relation: Relation}`-objects, containing all the relations that this feature is part of. " + | ||||
|         "\n\n" + | ||||
|         "For example: `_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')`", | ||||
|         [], | ||||
|         { | ||||
|             name: "memberships", | ||||
|             doc: "Gives a list of `{role: string, relation: Relation}`-objects, containing all the relations that this feature is part of. " + | ||||
|                 "\n\n" + | ||||
|                 "For example: `_part_of_walking_routes=feat.memberships().map(r => r.relation.tags.name).join(';')`", | ||||
|             args: [] | ||||
|         }, | ||||
|         (params, _) => { | ||||
|             return () => params.relations ?? []; | ||||
|         } | ||||
|     ) | ||||
| 
 | ||||
|     private static readonly allFuncs: ExtraFunction[] = [ExtraFunction.DistanceToFunc, ExtraFunction.OverlapFunc, ExtraFunction.ClosestObjectFunc, ExtraFunction.Memberships]; | ||||
|     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.Memberships, | ||||
|         ExtraFunction.AspectedRouting | ||||
|     ]; | ||||
|     private readonly _name: string; | ||||
|     private readonly _args: string[]; | ||||
|     private readonly _doc: string; | ||||
|     private readonly _f: (params: { featuresPerLayer: Map<string, any[]>, relations: { role: string, relation: Relation }[] }, feat: any) => any; | ||||
| 
 | ||||
|     constructor(name: string, doc: string, args: string[], f: ((params: { featuresPerLayer: Map<string, any[]>, relations: { role: string, relation: Relation }[] }, feat: any) => any)) { | ||||
|         this._name = name; | ||||
|         this._doc = doc; | ||||
|         this._args = args; | ||||
|     constructor(options: { name: string, doc: string, args: string[] }, | ||||
|                 f: ((params: { featuresPerLayer: Map<string, any[]>, relations: { role: string, relation: Relation }[] }, feat: any) => any)) { | ||||
|         this._name = options.name; | ||||
|         this._doc = options.doc; | ||||
|         this._args = options.args; | ||||
|         this._f = f; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static FullPatchFeature(featuresPerLayer: Map<string, any[]>, relations: { role: string, relation: Relation }[], feature) { | ||||
|  | @ -186,7 +222,6 @@ export class ExtraFunction { | |||
|     } | ||||
| 
 | ||||
|     public PatchFeature(featuresPerLayer: Map<string, any[]>, relations: { role: string, relation: Relation }[], feature: any) { | ||||
| 
 | ||||
|         feature[this._name] = this._f({featuresPerLayer: featuresPerLayer, relations: relations}, feature); | ||||
|         feature[this._name] = this._f({featuresPerLayer: featuresPerLayer, relations: relations}, feature) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -276,14 +276,61 @@ export class GeoOperations { | |||
|         } | ||||
|         return undefined; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Generates the closest point on a way from a given point | ||||
|      * @param way The road on which you want to find a point | ||||
|      * @param point Point defined as [lon, lat] | ||||
|      */ | ||||
|     public static nearestPoint(way, point: [number, number]){ | ||||
|     public static nearestPoint(way, point: [number, number]) { | ||||
|         return turf.nearestPointOnLine(way, point, {units: "kilometers"}); | ||||
|     } | ||||
| 
 | ||||
|     public static toCSV(features: any[]): string { | ||||
| 
 | ||||
|         const headerValuesSeen = new Set<string>(); | ||||
|         const headerValuesOrdered: string[] = [] | ||||
| 
 | ||||
|         function addH(key) { | ||||
|             if (!headerValuesSeen.has(key)) { | ||||
|                 headerValuesSeen.add(key) | ||||
|                 headerValuesOrdered.push(key) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         addH("_lat") | ||||
|         addH("_lon") | ||||
| 
 | ||||
|         const lines: string[] = [] | ||||
| 
 | ||||
|         for (const feature of features) { | ||||
|             const properties = feature.properties; | ||||
|             for (const key in properties) { | ||||
|                 if (!properties.hasOwnProperty(key)) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 addH(key) | ||||
| 
 | ||||
|             } | ||||
|         } | ||||
|         headerValuesOrdered.sort() | ||||
|         for (const feature of features) { | ||||
|             const properties = feature.properties; | ||||
|             let line = "" | ||||
|             for (const key of headerValuesOrdered) { | ||||
|                 const value = properties[key] | ||||
|                 if (value === undefined) { | ||||
|                     line += "," | ||||
|                 } else { | ||||
|                     line += JSON.stringify(value)+"," | ||||
|                 } | ||||
|             } | ||||
|             lines.push(line) | ||||
|         } | ||||
| 
 | ||||
|         return headerValuesOrdered.map(v => JSON.stringify(v)).join(",") + "\n" + lines.join("\n") | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,8 +27,8 @@ export default class MetaTagging { | |||
|                        relations: Map<string, { role: string, relation: Relation }[]>, | ||||
|                        layers: LayerConfig[], | ||||
|                        includeDates = true) { | ||||
|          | ||||
|         if(features === undefined || features.length === 0){ | ||||
| 
 | ||||
|         if (features === undefined || features.length === 0) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|  | @ -79,14 +79,10 @@ export default class MetaTagging { | |||
|                 } | ||||
| 
 | ||||
|             } | ||||
|              | ||||
|              | ||||
|              | ||||
|              | ||||
|              | ||||
|              | ||||
| 
 | ||||
| 
 | ||||
|         }) | ||||
|          | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  | @ -115,6 +111,17 @@ export default class MetaTagging { | |||
|                 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 | ||||
|                         } | ||||
|                          | ||||
|                         if (result === undefined || result === "") { | ||||
|                             return; | ||||
|                         } | ||||
|  | @ -124,11 +131,11 @@ export default class MetaTagging { | |||
|                         } | ||||
|                         feature.properties[key] = result; | ||||
|                     } catch (e) { | ||||
|                         if(MetaTagging. errorPrintCount < MetaTagging.stopErrorOutputAt){ | ||||
|                         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) | ||||
|                             MetaTagging.   errorPrintCount ++; | ||||
|                             if(MetaTagging. errorPrintCount == MetaTagging.stopErrorOutputAt){ | ||||
|                                 console.error("Got ",MetaTagging.stopErrorOutputAt," errors calculating this metatagging - stopping output now") | ||||
|                             MetaTagging.errorPrintCount++; | ||||
|                             if (MetaTagging.errorPrintCount == MetaTagging.stopErrorOutputAt) { | ||||
|                                 console.error("Got ", MetaTagging.stopErrorOutputAt, " errors calculating this metatagging - stopping output now") | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|  |  | |||
							
								
								
									
										194
									
								
								Logic/Osm/aspectedRouting.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								Logic/Osm/aspectedRouting.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,194 @@ | |||
| export default class AspectedRouting { | ||||
| 
 | ||||
|     public readonly name: string | ||||
|     public readonly description: string | ||||
|     public readonly units: string | ||||
|     public readonly program: any | ||||
| 
 | ||||
|     public constructor(program) { | ||||
|         this.name = program.name; | ||||
|         this.description = program.description; | ||||
|         this.units = program.unit | ||||
|         this.program = JSON.parse(JSON.stringify(program)) | ||||
|         delete this.program.name | ||||
|         delete this.program.description | ||||
|        delete this.program.unit | ||||
|     } | ||||
| 
 | ||||
|     public evaluate(properties){ | ||||
|         return AspectedRouting.interpret(this.program, properties) | ||||
|     } | ||||
|     /** | ||||
|      * Interprets the given Aspected-routing program for the given properties | ||||
|      */ | ||||
|     public static interpret(program: any, properties: any) { | ||||
|         if (typeof program !== "object") { | ||||
|             return program; | ||||
|         } | ||||
| 
 | ||||
|         let functionName /*: string*/ = undefined; | ||||
|         let functionArguments /*: any */ = undefined | ||||
|         let otherValues = {} | ||||
|         // @ts-ignore
 | ||||
|         Object.entries(program).forEach(tag => { | ||||
|                 const [key, value] = tag; | ||||
|                 if (key.startsWith("$")) { | ||||
|                     functionName = key | ||||
|                     functionArguments = value | ||||
|                 } else { | ||||
|                     otherValues[key] = value | ||||
|                 } | ||||
|             } | ||||
|         ) | ||||
| 
 | ||||
|         if (functionName === undefined) { | ||||
|             return AspectedRouting.interpretAsDictionary(program, properties) | ||||
|         } | ||||
| 
 | ||||
|         if (functionName === '$multiply') { | ||||
|             return AspectedRouting.multiplyScore(properties, functionArguments); | ||||
|         } else if (functionName === '$firstMatchOf') { | ||||
|             return AspectedRouting.getFirstMatchScore(properties, functionArguments); | ||||
|         } else if (functionName === '$min') { | ||||
|             return AspectedRouting.getMinValue(properties, functionArguments); | ||||
|         } else if (functionName === '$max') { | ||||
|             return AspectedRouting.getMaxValue(properties, functionArguments); | ||||
|         } else if (functionName === '$default') { | ||||
|             return AspectedRouting.defaultV(functionArguments, otherValues, properties) | ||||
|         } else { | ||||
|             console.error(`Error: Program ${functionName} is not implemented yet. ${JSON.stringify(program)}`); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given a 'program' without function invocation, interprets it as a dictionary | ||||
|      * | ||||
|      * E.g., given the program | ||||
|      * | ||||
|      * { | ||||
|      *     highway: { | ||||
|      *         residential: 30, | ||||
|      *         living_street: 20 | ||||
|      *     }, | ||||
|      *     surface: { | ||||
|      *         sett : 0.9 | ||||
|      *     } | ||||
|      *      | ||||
|      * } | ||||
|      * | ||||
|      * in combination with the tags {highway: residential}, | ||||
|      * | ||||
|      * the result should be [30, undefined]; | ||||
|      * | ||||
|      * For the tags {highway: residential, surface: sett} we should get [30, 0.9] | ||||
|      * | ||||
|      * | ||||
|      * @param program | ||||
|      * @param tags | ||||
|      * @return {(undefined|*)[]} | ||||
|      */ | ||||
|     private static interpretAsDictionary(program, tags) { | ||||
|         // @ts-ignore
 | ||||
|         return Object.entries(tags).map(tag => { | ||||
|             const [key, value] = tag; | ||||
|             const propertyValue = program[key] | ||||
|             if (propertyValue === undefined) { | ||||
|                 return undefined | ||||
|             } | ||||
|             if (typeof propertyValue !== "object") { | ||||
|                 return propertyValue | ||||
|             } | ||||
|             // @ts-ignore
 | ||||
|             return propertyValue[value] | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private static defaultV(subProgram, otherArgs, tags) { | ||||
|         // @ts-ignore
 | ||||
|         const normalProgram = Object.entries(otherArgs)[0][1] | ||||
|         const value = AspectedRouting.interpret(normalProgram, tags) | ||||
|         if (value !== undefined) { | ||||
|             return value; | ||||
|         } | ||||
|         return AspectedRouting.interpret(subProgram, tags) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Multiplies the default score with the proper values | ||||
|      * @param tags {object} the active tags to check against | ||||
|      * @param subprograms which should generate a list of values | ||||
|      * @returns score after multiplication | ||||
|      */ | ||||
|     private static multiplyScore(tags, subprograms) { | ||||
|         let number = 1 | ||||
| 
 | ||||
|         let subResults: any[] | ||||
|         if (subprograms.length !== undefined) { | ||||
|             subResults = AspectedRouting.concatMap(subprograms, subprogram => AspectedRouting.interpret(subprogram, tags)) | ||||
|         } else { | ||||
|             subResults = AspectedRouting.interpret(subprograms, tags) | ||||
|         } | ||||
| 
 | ||||
|         subResults.filter(r => r !== undefined).forEach(r => number *= parseFloat(r)) | ||||
|         return number.toFixed(2); | ||||
|     } | ||||
| 
 | ||||
|     private static getFirstMatchScore(tags, order: any) { | ||||
|         /*Order should be a list of arguments after evaluation*/ | ||||
|         order = <string[]>AspectedRouting.interpret(order, tags) | ||||
|         for (let key of order) { | ||||
|             // @ts-ignore
 | ||||
|             for (let entry of Object.entries(JSON.parse(tags))) { | ||||
|                 const [tagKey, value] = entry; | ||||
|                 if (key === tagKey) { | ||||
|                     // We have a match... let's evaluate the subprogram
 | ||||
|                     const evaluated = AspectedRouting.interpret(value, tags) | ||||
|                     if (evaluated !== undefined) { | ||||
|                         return evaluated; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Not a single match found...
 | ||||
|         return undefined | ||||
|     } | ||||
| 
 | ||||
|     private static getMinValue(tags, subprogram) { | ||||
|         const minArr = subprogram.map(part => { | ||||
|             if (typeof (part) === 'object') { | ||||
|                 const calculatedValue = this.interpret(part, tags) | ||||
|                 return parseFloat(calculatedValue) | ||||
|             } else { | ||||
|                 return parseFloat(part); | ||||
|             } | ||||
|         }).filter(v => !isNaN(v)); | ||||
|         return Math.min(...minArr); | ||||
|     } | ||||
| 
 | ||||
|     private static getMaxValue(tags, subprogram) { | ||||
|         const maxArr = subprogram.map(part => { | ||||
|             if (typeof (part) === 'object') { | ||||
|                 return parseFloat(AspectedRouting.interpret(part, tags)) | ||||
|             } else { | ||||
|                 return parseFloat(part); | ||||
|             } | ||||
|         }).filter(v => !isNaN(v)); | ||||
|         return Math.max(...maxArr); | ||||
|     } | ||||
| 
 | ||||
|     private static concatMap(list, f): any[] { | ||||
|         const result = [] | ||||
|         list = list.map(f) | ||||
|         for (const elem of list) { | ||||
|             if (elem.length !== undefined) { | ||||
|                 // This is a list
 | ||||
|                 result.push(...elem) | ||||
|             } else { | ||||
|                 result.push(elem) | ||||
|             } | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										42
									
								
								Logic/Tags/ComparingTag.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								Logic/Tags/ComparingTag.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| import {TagsFilter} from "./TagsFilter"; | ||||
| 
 | ||||
| export default class ComparingTag implements TagsFilter { | ||||
|     private readonly _key: string; | ||||
|     private readonly _predicate: (value: string) => boolean; | ||||
|     private readonly _representation: string; | ||||
|      | ||||
|     constructor(key: string, predicate : (value:string | undefined) => boolean, representation: string = "") { | ||||
|         this._key = key; | ||||
|         this._predicate = predicate; | ||||
|         this._representation = representation; | ||||
|     } | ||||
|      | ||||
|     asChange(properties: any): { k: string; v: string }[] { | ||||
|         throw "A comparable tag can not be used to be uploaded to OSM" | ||||
|     } | ||||
| 
 | ||||
|     asHumanString(linkToWiki: boolean, shorten: boolean, properties: any) { | ||||
|         return this._key+this._representation | ||||
|     } | ||||
| 
 | ||||
|     asOverpass(): string[] { | ||||
|         throw "A comparable tag can not be used as overpass filter" | ||||
|     } | ||||
| 
 | ||||
|     isEquivalent(other: TagsFilter): boolean { | ||||
|         return other === this; | ||||
|     } | ||||
| 
 | ||||
|     isUsableAsAnswer(): boolean { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     matchesProperties(properties: any): boolean { | ||||
|         return this._predicate(properties[this._key]); | ||||
|     } | ||||
| 
 | ||||
|     usedKeys(): string[] { | ||||
|         return [this._key]; | ||||
|     } | ||||
|      | ||||
| } | ||||
|  | @ -1,7 +1,6 @@ | |||
| import {Utils} from "../../Utils"; | ||||
| import {RegexTag} from "./RegexTag"; | ||||
| import {TagsFilter} from "./TagsFilter"; | ||||
| import {TagUtils} from "./TagUtils"; | ||||
| 
 | ||||
| export class Tag extends TagsFilter { | ||||
|     public key: string | ||||
|  | @ -46,11 +45,6 @@ export class Tag extends TagsFilter { | |||
|         } | ||||
|         return [`["${this.key}"="${this.value}"]`]; | ||||
|     } | ||||
| 
 | ||||
|     substituteValues(tags: any) { | ||||
|         return new Tag(this.key, TagUtils.ApplyTemplate(this.value as string, tags)); | ||||
|     } | ||||
| 
 | ||||
|     asHumanString(linkToWiki?: boolean, shorten?: boolean) { | ||||
|         let v = this.value; | ||||
|         if (shorten) { | ||||
|  |  | |||
|  | @ -166,4 +166,21 @@ export class UIEventSource<T> { | |||
|             } | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export class UIEventSourceTools { | ||||
| 
 | ||||
|     private static readonly _download_cache = new Map<string, UIEventSource<any>>() | ||||
| 
 | ||||
|     public static downloadJsonCached(url: string): UIEventSource<any>{ | ||||
|         const cached = UIEventSourceTools._download_cache.get(url) | ||||
|         if(cached !== undefined){ | ||||
|             return cached; | ||||
|         } | ||||
|         const src = new UIEventSource<any>(undefined) | ||||
|         UIEventSourceTools._download_cache.set(url, src) | ||||
|         Utils.downloadJson(url).then(r => src.setData(r)) | ||||
|         return src; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue