| 
									
										
										
										
											2021-07-27 12:11:42 +02:00
										 |  |  | 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 | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |         delete this.program.unit | 
					
						
							| 
									
										
										
										
											2021-07-27 12:11:42 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * 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)}` | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2021-07-27 12:11:42 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * 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) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2021-07-27 12:11:42 +02:00
										 |  |  |         } 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 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |     public evaluate(properties) { | 
					
						
							|  |  |  |         return AspectedRouting.interpret(this.program, properties) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-07-27 12:11:42 +02:00
										 |  |  | } |