forked from MapComplete/MapComplete
		
	
		
			
	
	
		
			194 lines
		
	
	
	
		
			6.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			194 lines
		
	
	
	
		
			6.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 
								 | 
							
								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;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 |