Compare commits
12 commits
master
...
feature/js
Author | SHA1 | Date | |
---|---|---|---|
5821db3b55 | |||
c4cd9d1806 | |||
2cc170395c | |||
|
9aba3939c3 | ||
|
0dafbabc23 | ||
|
9c31a92abd | ||
|
2ba01713ef | ||
|
2289827a9d | ||
|
00b6f66110 | ||
|
a751490e4b | ||
77fa88dfd4 | |||
12fc26fa44 |
13 changed files with 10258 additions and 1 deletions
11
.gitignore
vendored
11
.gitignore
vendored
|
@ -4,3 +4,14 @@
|
|||
.~lock.*
|
||||
output/*
|
||||
AspectedRouting.sln.DotSettings
|
||||
|
||||
#Visual Studio Code
|
||||
.vscode
|
||||
|
||||
# Node Modules
|
||||
*/node_modules
|
||||
|
||||
.fake
|
||||
.ionide
|
||||
|
||||
*/.routeExamples
|
|
@ -33,7 +33,7 @@ To call a function in an aspect, one creates a hash in the JSON where exactly on
|
|||
}
|
||||
```
|
||||
|
||||
Interpreting the above expression will aways yield `no` when evaluating, as the parameters have different values. The type of the above expression is thus `Bool`.
|
||||
Interpreting the above expression will always yield `no` when evaluating, as the parameters have different values. The type of the above expression is thus `Bool`.
|
||||
|
||||
If no key has a function invocation (thus no key starts with `$`), the hash is interpreted as a mapping:
|
||||
|
||||
|
|
3
javascript/.babelrc
Normal file
3
javascript/.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": ["@babel/preset-env"]
|
||||
}
|
17
javascript/.npmignore
Normal file
17
javascript/.npmignore
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Node modules
|
||||
node_modules
|
||||
npm_debug.log
|
||||
|
||||
# Git
|
||||
.git
|
||||
|
||||
# Apple
|
||||
.DS_Store
|
||||
|
||||
|
||||
# Route Examples
|
||||
.routeExamples
|
||||
.tagExamples
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode
|
38
javascript/.tagExamples/example.js
Normal file
38
javascript/.tagExamples/example.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Example tags
|
||||
*/
|
||||
const tags1 = {
|
||||
"highway": "residential", // 1 // Expect "yes"
|
||||
"surface": "paved", // 0.99
|
||||
}
|
||||
|
||||
const tags2 = {
|
||||
"bicycle": "yes", // Expect "yes"
|
||||
"cycleway": "lane",
|
||||
"highway": "secondary",
|
||||
"maxspeed": "50",
|
||||
}
|
||||
|
||||
const tags3 = {
|
||||
"cyclestreet": "yes",
|
||||
"highway": "residential", // Expect "yes"
|
||||
"maxspeed": "30",
|
||||
"surface": "asphalt"
|
||||
}
|
||||
|
||||
const tags4 = {
|
||||
"highway": "track", // Expect "yes"
|
||||
"surface": "asphalt",
|
||||
"incline": "10%"
|
||||
}
|
||||
|
||||
const tags5 = {
|
||||
"access": "no", // Expect "no"
|
||||
"bicycle": "official",
|
||||
"area": "yes"
|
||||
}
|
||||
|
||||
const tags6 = {
|
||||
"surface":"dirt",
|
||||
"highway":"track",
|
||||
}
|
220
javascript/BuildingAProfile.md
Normal file
220
javascript/BuildingAProfile.md
Normal file
|
@ -0,0 +1,220 @@
|
|||
# Building a routeplanner
|
||||
|
||||
This document was originally written as blog post. It gives a practical, example first example to build a custom route planner.
|
||||
|
||||
In order to deploy:
|
||||
|
||||
- Build your profile by creating the relevant `.json`-files in a directory; take a peek at `Examples`
|
||||
- Run the project: `cd AspectedRouting && dotnet run <inputdir> <outputdir>` (make sure the outputDirectory is _not_ a subdirectory of the input directory)
|
||||
- In outputDir, you will find a bunch of lua-scripts which can be used with itinero
|
||||
|
||||
## A step by step example for an aspect
|
||||
|
||||
Let us recreate (a small part) of the legal access aspect for cyclists. The file will answer the question: __can a bicycle enter this road?__
|
||||
|
||||
First, we start with some metadata:
|
||||
|
||||
```
|
||||
"name": "bicycle.legal_access",
|
||||
"description": "Can a bicycle enter this road segment?",
|
||||
"unit": "Yes, No",
|
||||
```
|
||||
|
||||
The `name` field is an important one, as the aspect can be called with it in other files. The `description` and `unit`-fields however are purely as documentation - but are nonetheless important. Writing down exactly what an aspect means helps to clarify what is calculated before coding it and makes life easier down the road.
|
||||
|
||||
### Building the access-function
|
||||
|
||||
To call a function in an aspect, one creates a hash in the JSON where exactly one key starts with a `$`. The rest of the key determines which function is called, the value of the key is its first argument whereas the other keys in the hash function as other parameters. One could for example check that two values are the same with:
|
||||
|
||||
```
|
||||
{
|
||||
"$eq": "someValue",
|
||||
"b": "otherValue"
|
||||
}
|
||||
```
|
||||
|
||||
Interpreting the above expression will aways yield `no` when evaluating, as the parameters have different values. The type of the above expression is thus `Bool`.
|
||||
|
||||
If no key has a function invocation (thus no key starts with `$`), the hash is interpreted as a mapping:
|
||||
|
||||
```
|
||||
{
|
||||
"yes": "yes"
|
||||
"no": "no"
|
||||
"customers": "no"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The above expression is a function of type `string -> double`. If invoked, it will convert `yes` into the value `yes` and `customers` into the value `no`. Any string not in the mapping will result in `null`.
|
||||
|
||||
|
||||
Every expression in AspectedRouting is implicitly yet strongly typed at compile time. Having types around is cool and good for correctness, but can be constraining and the cause of boilerplate. Therefore, expressions are allowed to have _multiple_ types. Due to the context of how it is called and what the parameters of functions are, the compiler can determine exaclty which type is meant.
|
||||
|
||||
For example, a mapping like above can also be used to match OSM-keys:
|
||||
|
||||
```
|
||||
{
|
||||
"access": {
|
||||
"yes": "no",
|
||||
"no": "no",
|
||||
"customers":"no"
|
||||
},
|
||||
"bicycle": "$id",
|
||||
"construction": "no"
|
||||
}
|
||||
```
|
||||
|
||||
There is a lot to unpack here. A mapping as above is either a function taking a `string` and returning a value, or it is a function taking a `Tags`-collection and returning a collection of calculated values.
|
||||
|
||||
For example, passing in the collection `access=customers` in the above function will result into the value `["no"]`. Passing `access=dismount;bicycle=yes` will result in `[null, "yes"]` - the value corresponding with `access` is passed into the mapping `{"yes":"yes", "customers":"no", ...}` where no match is found resulting in `null`. The value for `bicycle` is passed into the `$id` function which simply passes back its argument.
|
||||
|
||||
At last, there is the cryptical `"construction":"no"`. This expression indicates that if a construction-tag is present, the resulting value should always be `no`. But how does it work exactly? When writing a constant (such as `"no"`) in an Aspected-Routing file, it is interpreted as either being the literal constant _or_ as being a function which ignores the parameter! `"no"` has thus the types `string` and `a -> string`. When used in a single mapping with type `string -> string` it is clear the first one is meant, when used in a tagsmapping with type `Tags -> string` (e.g. `{"key": "f"}`, the type of the function `f` should be `string -> b`, clearly indicating that `"no"` should be interpreted as the function which ignores the parameter. If this sounds like magic to you - don't worry about it too much. In practice, you just type what feels logical and it'll work out.
|
||||
|
||||
#### Combining multiple tags
|
||||
|
||||
The above aspect is already pretty close to a working access-calculation for cyclists - but we still have a collection of values, not a single one. We have a clear order in which we want to evaluate the tags. This too can be done with a builtin function, namely `$firstMatchOf` with the type `[string] -> (Tags -> [a]) -> (Tags -> a)`. For those not familiar with this notation for the types, this reads as: given a list of `string` and a function (which converts tags into a list of `a`), I'll give back a function that converts `Tags` into some `a`
|
||||
|
||||
It is used in the following way:
|
||||
|
||||
```
|
||||
{
|
||||
"$firstMatchOf":["bicycle", "construction", "access"],
|
||||
"f": { ... above code ... }
|
||||
}
|
||||
```
|
||||
|
||||
At last, what if _none_ of the tags match? What do we do then? For that, there is `$default: a -> (x -> a) -> (x -> a)`. More comprehensively, this function needs a (default) value `a`, and a function calculating some `a` based on `x` and it'll give back a function that calculates an `a` based on an `x`.
|
||||
|
||||
Here too is an example clearer then trying to explain it:
|
||||
|
||||
```
|
||||
{
|
||||
"$default": "no",
|
||||
"f": { ... above code ... }
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Combining everything
|
||||
|
||||
Everything together, this gives a very basic implementation of where a cyclists can cycle! If we throw it all together, we get the following JSON file:
|
||||
|
||||
|
||||
```
|
||||
{
|
||||
|
||||
"name": "bicycle.legal_access",
|
||||
"description": "Gives, for each type of highway, whether or not a normal bicycle can enter legally.\nNote that legal access is a bit 'grey' in the case of roads marked private and permissive, in which case these values are returned ",
|
||||
"unit": "yes, no",
|
||||
|
||||
|
||||
"$default": "no",
|
||||
"f": {
|
||||
"$firstMatchOf": ["bicycle", "construction", "access"],
|
||||
"f": {
|
||||
"access": {
|
||||
"yes": "no",
|
||||
"no": "no",
|
||||
"customers":"no"
|
||||
},
|
||||
"bicycle": "$id",
|
||||
"construction": "no"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
It should be noted that the _actual_ implementation is more complicated then that. There are more tags to keep track of, but the above explanation should be enough to get a grasp of [legal-access-aspect for bicycles](https://github.com/pietervdvn/AspectedRouting/blob/master/Examples/bicycle/aspects/bicycle.legal_access.json). An overview of all the functions and available types, have a look [here](https://github.com/pietervdvn/AspectedRouting/blob/master/Examples/TypesAndFunctions.md)
|
||||
|
||||
### Building a profile
|
||||
|
||||
Having accessibility alone isn't enough to create a route planner for cyclists. In a similar way, one can create an aspect that defines [if the street is a oneway](https://github.com/pietervdvn/AspectedRouting/blob/master/Examples/bicycle/aspects/bicycle.oneway.json) or how [comfortable a street is](https://github.com/pietervdvn/AspectedRouting/blob/master/Examples/bicycle/aspects/bicycle.comfort.json). (Please note that the linked examples are stripped down examples. Our actual routeplanner has a few more aspect files and more tags).
|
||||
|
||||
At last, we have to combine those aspects into something that actually creates the profile. This is done by another JSON-file, such as [this one](https://github.com/pietervdvn/AspectedRouting/blob/master/Examples/bicycle/bicycle.json). Lets break it down:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "bicycle",
|
||||
"description": "Profile for a normal bicycle",
|
||||
```
|
||||
|
||||
This is some metadata, mostly meant for humans.
|
||||
|
||||
```
|
||||
|
||||
"defaults": {
|
||||
"#maxspeed": 20,
|
||||
"#timeNeeded": 0,
|
||||
"#comfort": 0,
|
||||
"#distance": 0,
|
||||
},
|
||||
```
|
||||
|
||||
This declares some variables, which can only be used in the scope of the profile. Variables always start with `#` and are either a `number`, a `boolean` or a `string`. They are used to below the actual aspects of the profile:
|
||||
|
||||
```
|
||||
"access": "$bicycle.legal_access",
|
||||
```
|
||||
This states when a segment is accessible. It expects a function `Tags -> string` and a segment is considered not accessible if this value is `"no"`; it is accessible otherwise.
|
||||
|
||||
```
|
||||
"oneway": "$bicycle.oneway",
|
||||
```
|
||||
This indicates if the street is a oneway, it expects a function `Tags -> string` where the resulting value is one of `both`,`with` or `against`
|
||||
|
||||
|
||||
```
|
||||
"speed": {
|
||||
"$min": [
|
||||
"#defaultSpeed",
|
||||
"$legal_maxspeed_be"
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
This states how fast a bicycle would be going on the segment; it expects a function `Tags -> number`. It is the first interesting case: both the variable `#maxspeed` (defined in `defaults`) is used, together with a function calculating the _legal_ max speed for a road segment. The lowest of the two is taken, by the function `$min`
|
||||
|
||||
```
|
||||
"behaviours": {
|
||||
"fastest": {
|
||||
"description": "The fastest route to your destination",
|
||||
"#timeNeeded": 1,
|
||||
},
|
||||
"shortest": {
|
||||
"description": "The shortest route, independent of of speed",
|
||||
"#distance": 1,
|
||||
},
|
||||
"comfort": {
|
||||
"description": "A comfortable route preferring well-paved roads, smaller roads and a bit of scenery at the cost of speed",
|
||||
"#comfort": 1
|
||||
},
|
||||
"electric":{
|
||||
"description": "An electrical bicycle",
|
||||
"#maxspeed": 25,
|
||||
"#comfort":1,
|
||||
"#timeNeeded": 5
|
||||
},
|
||||
"electric_fastest":{
|
||||
"description": "An electrical bicycle, focussed on speed",
|
||||
"#maxspeed": 25,
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
The above code defines _behaviours_ of the cyclist. It allows to overwrite a variable which influences the routeplanning. For example, the behavour `electrical` above will overwrite the maxspeed, changing the `speed`-aspect at the top of the file. However, these variables are most important in the priority below:
|
||||
|
||||
```
|
||||
"priority": {
|
||||
"#comfort": "$bicycle.comfort",
|
||||
"#timeNeeded": "$speed",
|
||||
"#distance": "$distance",
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The priority is the core of the customizibility and calculates the priority of the segment. First, the function on the right is calculated with the tags of the segment - e.g. for a segment with tags `highway=residential;surface=sett;` this will yield `{"#comfort": 0.9, "#timeNeeded": 25, "#distance": 1}`.
|
||||
|
||||
These values are multiplied with the variables and summed, giving the _priority_ of the segment - where the variable are set by the requested profile; e.g. for `electrical` this will yield `(#comfort = 1) * 0.9 + (#timeNeeded = 5) * 25 + (#distance = 0) * 1`, giving the priority of `125.9`. The _cost_ per meter is then the inverted value, thus `1 / 125.9` or approximately `0.008/m`. This cost seems relatively low - but that doesn't matter as all costs are in the same range.
|
23
javascript/README.md
Normal file
23
javascript/README.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# StressMap interpreter
|
||||
CLI program that generates a stress score based on a RuleSet JSON file and a tag object.
|
||||
|
||||
## Installation
|
||||
`npm i mapcomplete-stressmap`
|
||||
|
||||
## How to use
|
||||
This program can be used from the command line interface:
|
||||
`node mapcomplete-stressmap [ruleset.json] [tags object]`
|
||||
|
||||
Example tags:
|
||||
```json
|
||||
{
|
||||
"cyclestreet": "yes",
|
||||
"highway": "residential", // Expect "yes"
|
||||
"maxspeed": "30",
|
||||
"surface": "asphalt"
|
||||
}
|
||||
```
|
||||
|
||||
Guidelines on JSON Ruleset (from [AspectedRouting](https://www.github.com/pietervdvn/AspectedRouting.git))
|
||||
|
||||
[Building a Profile](./BuildingAProfile.md)
|
191
javascript/RuleSet.js
Normal file
191
javascript/RuleSet.js
Normal file
|
@ -0,0 +1,191 @@
|
|||
/**
|
||||
* RuleSet Class
|
||||
* Constructor
|
||||
* @param name {string} Name of RuleSet
|
||||
* @param defaultValue {number} Default score value
|
||||
* @param values {object} Main data object
|
||||
*/
|
||||
class RuleSet {
|
||||
constructor(config) {
|
||||
delete config["name"]
|
||||
delete config["unit"]
|
||||
delete config["description"]
|
||||
this.program = config
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* getScore calculates a score for the RuleSet
|
||||
* @param tags {object} Active tags to compare against
|
||||
*/
|
||||
runProgram(tags, program = this.program) {
|
||||
if (typeof program !== "object") {
|
||||
return program;
|
||||
}
|
||||
|
||||
let functionName /*: string*/ = undefined;
|
||||
let functionArguments /*: any */ = undefined
|
||||
let otherValues = {}
|
||||
Object.entries(program).forEach(
|
||||
entry => {
|
||||
const [key, value] = entry
|
||||
if (key.startsWith("$")) {
|
||||
functionName = key
|
||||
functionArguments = value
|
||||
} else {
|
||||
otherValues[key] = value
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (functionName === undefined) {
|
||||
return this.interpretAsDictionary(program, tags)
|
||||
}
|
||||
|
||||
if (functionName === '$multiply') {
|
||||
return this.multiplyScore(tags, functionArguments);
|
||||
} else if (functionName === '$firstMatchOf') {
|
||||
this.order = keys;
|
||||
return this.getFirstMatchScore(tags);
|
||||
} else if (functionName === '$min') {
|
||||
return this.getMinValue(tags, functionArguments);
|
||||
} else if (functionName === '$max') {
|
||||
return this.getMaxValue(tags, functionArguments);
|
||||
} else if (functionName === '$default') {
|
||||
return this.defaultV(functionArguments, otherValues, tags)
|
||||
} 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|*)[]}
|
||||
*/
|
||||
interpretAsDictionary(program, tags) {
|
||||
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
|
||||
}
|
||||
return propertyValue[value]
|
||||
});
|
||||
}
|
||||
|
||||
defaultV(subProgram, otherArgs, tags) {
|
||||
const normalProgram = Object.entries(otherArgs)[0][1]
|
||||
const value = this.runProgram(tags, normalProgram)
|
||||
if (value !== undefined) {
|
||||
return value;
|
||||
}
|
||||
return this.runProgram(tags, subProgram)
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies the default score with the proper values
|
||||
* @param tags {object} the active tags to check against
|
||||
* @param subprogram which should generate a list of values
|
||||
* @returns score after multiplication
|
||||
*/
|
||||
multiplyScore(tags, subprogram) {
|
||||
let number = 1
|
||||
this.runProgram(tags, subprogram).filter(r => r !== undefined).forEach(r => number *= parseFloat(r))
|
||||
return number.toFixed(2);
|
||||
}
|
||||
|
||||
getFirstMatchScore(tags) {
|
||||
let matchFound = false;
|
||||
let match = "";
|
||||
let i = 0;
|
||||
|
||||
for (let key of this.order) {
|
||||
i++;
|
||||
for (let entry of Object.entries(JSON.parse(tags))) {
|
||||
const [tagKey, tagValue] = entry;
|
||||
|
||||
if (key === tagKey) {
|
||||
const valueReply = this.checkValues(entry);
|
||||
|
||||
if (!!valueReply) {
|
||||
match = valueReply;
|
||||
matchFound = true;
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchFound) {
|
||||
match = this.defaultValue;
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
checkValues(tag) {
|
||||
const [tagKey, tagValue] = tag;
|
||||
const options = Object.entries(this.scoreValues[1])
|
||||
|
||||
for (let option of options) {
|
||||
const [optKey, optValues] = option;
|
||||
|
||||
if (optKey === tagKey) {
|
||||
return optValues[`${tagValue}`];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getMinValue(tags, subprogram) {
|
||||
console.log("Running min with", tags, subprogram)
|
||||
const minArr = subprogram.map(part => {
|
||||
if (typeof (part) === 'object') {
|
||||
const calculatedValue = this.runProgram(tags, part)
|
||||
return parseFloat(calculatedValue)
|
||||
} else {
|
||||
return parseFloat(part);
|
||||
}
|
||||
}).filter(v => !isNaN(v));
|
||||
return Math.min(...minArr);
|
||||
}
|
||||
|
||||
getMaxValue(tags, subprogram) {
|
||||
const maxArr = subprogram.map(part => {
|
||||
if (typeof (part) === 'object') {
|
||||
return parseFloat(this.runProgram(tags, part))
|
||||
} else {
|
||||
return parseFloat(part);
|
||||
}
|
||||
}).filter(v => !isNaN(v));
|
||||
return Math.max(...maxArr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default RuleSet;
|
68
javascript/__tests__/ruleSetTests.js
Normal file
68
javascript/__tests__/ruleSetTests.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
import { expect, jest } from '@jest/globals';
|
||||
import RuleSet from '../RuleSet.js';
|
||||
|
||||
describe('RuleSet', () => {
|
||||
const exampleJSON = {
|
||||
"name": "real.name",
|
||||
"$default": "1",
|
||||
"value": {
|
||||
"$multiply": {
|
||||
"example": {
|
||||
"score": "1.2"
|
||||
},
|
||||
"other": {
|
||||
"thing": "1",
|
||||
"something": "1.2",
|
||||
"other_thing": "1.4"
|
||||
}
|
||||
},
|
||||
"$firstMatchOf": [
|
||||
"area",
|
||||
"empty",
|
||||
"things"
|
||||
],
|
||||
"value": {
|
||||
"area": {
|
||||
"yes": "no"
|
||||
},
|
||||
"access:": {
|
||||
"no": "no",
|
||||
"customers": "private"
|
||||
},
|
||||
"$multiply": {
|
||||
"access": {
|
||||
"dismount": 0.15
|
||||
},
|
||||
"highway": {
|
||||
"path": 0.5
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
const tags = {
|
||||
"example": "score",
|
||||
"other": "something",
|
||||
"highway": "track",
|
||||
"surface": "sett",
|
||||
"cycleway": "lane"
|
||||
}
|
||||
test('it should resolve', () => {
|
||||
const ruleSet = exampleJSON;
|
||||
const { name, $default, $multiply, value} = ruleSet;
|
||||
|
||||
if (!!value) {
|
||||
const currentSet = new RuleSet(name, $default, value);
|
||||
currentSet.runProgram(tags);
|
||||
expect(currentSet.runProgram).resolves;
|
||||
expect(currentSet.toString).resolves;
|
||||
} else {
|
||||
const currentSet = new RuleSet(name, $default, {$multiply});
|
||||
currentSet.toString();
|
||||
currentSet.runProgram(tags);
|
||||
}
|
||||
|
||||
})
|
||||
})
|
7
javascript/index.js
Normal file
7
javascript/index.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import interpret from './interpret.js';
|
||||
import RuleSet from './RuleSet.js';
|
||||
|
||||
export {
|
||||
interpret,
|
||||
RuleSet
|
||||
};
|
37
javascript/interpret.js
Normal file
37
javascript/interpret.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Import packages
|
||||
*/
|
||||
import fs from 'fs';
|
||||
import {argv} from 'process';
|
||||
import RuleSet from './RuleSet.js';
|
||||
|
||||
const app = {
|
||||
init() {
|
||||
if (!!argv && argv.length < 4) {
|
||||
console.info(`Invalid command. In order to run the JavaScript interpreter please use the following format:
|
||||
> node index.js [ruleset JSON file] [tags]`)
|
||||
} else if (!!argv && argv.length === 4) {
|
||||
const definitionFile = argv[2];
|
||||
const tags = argv[3];
|
||||
const result = this.interpret(definitionFile, tags);
|
||||
console.log(result)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Interpret JsonFile and apply it as a tag. To use with CLI only
|
||||
* @param definitionFile {any} JSON input defining the score distribution
|
||||
* @param tags {any} OSM tags as key/value pairs
|
||||
*/
|
||||
interpret(definitionFile, tags) {
|
||||
if (typeof tags === "string") {
|
||||
tags = JSON.parse(tags)
|
||||
}
|
||||
const rawData = fs.readFileSync(definitionFile);
|
||||
const program = JSON.parse(rawData);
|
||||
return new RuleSet(program).runProgram(tags)
|
||||
}
|
||||
};
|
||||
|
||||
app.init();
|
||||
|
||||
export default app;
|
9616
javascript/package-lock.json
generated
Normal file
9616
javascript/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
26
javascript/package.json
Normal file
26
javascript/package.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "aspected-routing",
|
||||
"version": "0.2.0",
|
||||
"description": "Calculates a score based on a .Json-file which contains an Aspected-Routing function. This is a (partial) javascript port",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "jest"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.jsx?$": "babel-jest"
|
||||
}
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Charlotte Delvaux",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.14.7",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/node": "^16.3.1",
|
||||
"babel-jest": "^27.0.6",
|
||||
"jest": "^27.0.6",
|
||||
"ts-node": "^10.1.0"
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue