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)
|
|
}
|
|
}
|