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 } /** * 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 = 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 } public evaluate(properties) { return AspectedRouting.interpret(this.program, properties) } }