mapcomplete/Logic/Osm/aspectedRouting.ts

195 lines
No EOL
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
}
/**
* 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)
}
}