forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			200 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			200 lines
		
	
	
	
		
			6.4 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
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 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
 | 
						|
    }
 | 
						|
 | 
						|
    public evaluate(properties) {
 | 
						|
        return AspectedRouting.interpret(this.program, properties)
 | 
						|
    }
 | 
						|
}
 |