Compare commits

...

21 Commits

Author SHA1 Message Date
Siarhei Fedartsou
3e68237e03 Fix CI 2023-08-22 20:29:21 +02:00
Siarhei Fedartsou
2016d468bb Merge branch 'master' into sf-osrm-routed 2023-08-22 17:55:10 +02:00
Siarhei Fedartsou
38e4b0b9d6 Implement NodeJS based server fully replicating osrm-routed 2022-10-29 13:26:40 +02:00
Siarhei Fedartsou
ddde7e1ea6 Implement NodeJS based server fully replicating osrm-routed 2022-10-29 13:24:48 +02:00
Siarhei Fedartsou
4dfae26be7 add cluster 2022-10-26 19:30:27 +02:00
Siarhei Fedartsou
40805f7058 Implement NodeJS based server fully replicating osrm-routed 2022-10-25 20:26:20 +02:00
Siarhei Fedartsou
8e3fa70eb0 Implement NodeJS based server fully replicating osrm-routed 2022-10-25 15:54:59 +02:00
Siarhei Fedartsou
41ff79aa1d Implement NodeJS based server fully replicating osrm-routed 2022-10-23 17:00:36 +02:00
Siarhei Fedartsou
6dfb7d4ede Implement NodeJS based server fully replicating osrm-routed 2022-10-23 16:57:01 +02:00
Siarhei Fedartsou
71a638a14f Implement NodeJS based server fully replicating osrm-routed 2022-10-23 16:42:47 +02:00
Siarhei Fedartsou
4fd4c3b563 Implement NodeJS based server fully replicating osrm-routed 2022-10-23 16:24:07 +02:00
Siarhei Fedartsou
242c07443a Implement NodeJS based server fully replicating osrm-routed 2022-10-23 16:07:34 +02:00
Siarhei Fedartsou
1047cc1bfd Implement NodeJS based server fully replicating osrm-routed 2022-10-23 16:06:13 +02:00
Siarhei Fedartsou
9c7d75d7d4 Implement NodeJS based server fully replicating osrm-routed 2022-10-23 16:01:57 +02:00
Siarhei Fedartsou
3f0ded653d Implement NodeJS based server fully replicating osrm-routed 2022-10-23 16:01:13 +02:00
Siarhei Fedartsou
61810b9205 Implement NodeJS based server fully replicating osrm-routed 2022-10-23 15:59:51 +02:00
Siarhei Fedartsou
8273e8b0e2 Implement NodeJS based server fully replicating osrm-routed 2022-10-23 15:13:42 +02:00
Siarhei Fedartsou
dd04760982 Implement NodeJS based server fully replicating osrm-routed 2022-10-23 14:38:42 +02:00
Siarhei Fedartsou
768c33127a Implement NodeJS based server fully replicating osrm-routed 2022-10-23 14:26:10 +02:00
Siarhei Fedartsou
251ffe4a50 Implement NodeJS based server fully replicating osrm-routed 2022-10-23 14:18:52 +02:00
Siarhei Fedartsou
53f2da5d5e Implement NodeJS based server fully replicating osrm-routed 2022-10-23 13:56:21 +02:00
43 changed files with 4506 additions and 30 deletions

View File

@ -97,6 +97,29 @@ jobs:
npm run docs && ./scripts/error_on_dirty.sh
npm audit --production
routed-js:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 14
- name: Enable Node.js cache
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Compile routed-js
run: |
pushd routed-js
npm ci
npx tsc
popd
./scripts/error_on_dirty.sh
docker-image:
needs: format-taginfo-docs
runs-on: ubuntu-22.04
@ -512,6 +535,9 @@ jobs:
cmake .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE}
make --jobs=${JOBS}
popd
- name: Install osrm-routed-js dependencies
if: ${{ matrix.node != 12 && matrix.ENABLE_APPLE_SILICON != 'ON' && matrix.TARGET_ARCH != 'i686' }}
run: cd routed-js && npm ci && npm link && osrm-routed-js --version
- name: Run all tests
if: ${{ matrix.NODE_PACKAGE_TESTS_ONLY != 'ON' && matrix.ENABLE_APPLE_SILICON != 'ON' }}
run: |
@ -622,6 +648,6 @@ jobs:
ci-complete:
runs-on: ubuntu-22.04
needs: [build-test-publish, docker-image, windows-release-node]
needs: [build-test-publish, docker-image, routed-js, windows-release-node]
steps:
- run: echo "CI complete"

View File

@ -3,5 +3,7 @@ module.exports = {
ch: '--strict --tags ~@stress --tags ~@todo --tags ~@mld -f progress --require features/support --require features/step_definitions',
todo: '--strict --tags @todo --require features/support --require features/step_definitions',
all: '--strict --require features/support --require features/step_definitions',
mld: '--strict --tags ~@stress --tags ~@todo --tags ~@ch --require features/support --require features/step_definitions -f progress'
mld: '--strict --tags ~@stress --tags ~@todo --tags ~@ch --require features/support --require features/step_definitions -f progress',
verify_routed_js: '--strict --tags ~@skip_on_routed_js --tags ~@stress --tags ~@todo --tags ~@mld-only -f progress --require features/support --require features/step_definitions',
mld_routed_js: '--strict --tags ~@skip_on_routed_js --tags ~@stress --tags ~@todo --tags ~@ch --require features/support --require features/step_definitions -f progress',
};

View File

@ -188,11 +188,16 @@ class OSRMLoader {
this.loader = {shutdown: (cb) => cb() };
}
if (this.method === 'datastore') {
// shutdown only if we are switching from another loader type
if (this.loader !== this.sharedLoader) {
this.loader.shutdown((err) => {
if (err) return callback(err);
this.loader = this.sharedLoader;
this.sharedLoader.load(inputFile, callback);
});
} else {
this.sharedLoader.load(inputFile, callback);
}
} else if (this.method === 'directly') {
this.loader.shutdown((err) => {
if (err) return callback(err);
@ -206,7 +211,7 @@ class OSRMLoader {
this.mmapLoader.load(inputFile, callback);
});
} else {
callback(new Error('*** Unknown load method ' + method));
callback(new Error('*** Unknown load method ' + this.method));
}
}

View File

@ -4,6 +4,7 @@ Feature: osrm-routed command line options: help
Background:
Given the profile "testbot"
@skip_on_routed_js
Scenario: osrm-routed - Help should be shown when no options are passed
When I run "osrm-routed"
Then stderr should be empty
@ -25,6 +26,7 @@ Feature: osrm-routed command line options: help
And stdout should contain "--default-radius"
And it should exit successfully
@skip_on_routed_js
Scenario: osrm-routed - Help, short
When I run "osrm-routed -h"
Then stderr should be empty
@ -46,6 +48,8 @@ Feature: osrm-routed command line options: help
And stdout should contain "--default-radius"
And it should exit successfully
@skip_on_routed_js
Scenario: osrm-routed - Help, long
When I run "osrm-routed --help"
Then stderr should be empty

View File

@ -4,6 +4,7 @@ Feature: osrm-routed command line options: invalid options
Background:
Given the profile "testbot"
@skip_on_routed_js
Scenario: osrm-routed - Non-existing option
When I try to run "osrm-routed --fly-me-to-the-moon"
Then stdout should be empty
@ -11,6 +12,7 @@ Feature: osrm-routed command line options: invalid options
And stderr should contain "fly-me-to-the-moon"
And it should exit with an error
@skip_on_routed_js
Scenario: osrm-routed - Missing file
When I try to run "osrm-routed over-the-rainbow.osrm"
Then stderr should contain "over-the-rainbow.osrm"

View File

@ -33,6 +33,11 @@ module.exports = function () {
callback();
});
this.Then(/^HTTP code should be (\d+)$/, (code, callback) => {
assert(this.response.statusCode, parseInt(code));
callback();
});
this.Then(/^status message should be "(.*?)"$/, (message, callback) => {
try {
this.json = JSON.parse(this.response.body);

View File

@ -15,6 +15,8 @@ module.exports = function () {
this.setDefaultTimeout(this.TIMEOUT);
this.ROOT_PATH = process.cwd();
this.USE_ROUTED_JS = process.env.OSRM_USE_ROUTED_JS || false;
this.TEST_PATH = path.resolve(this.ROOT_PATH, 'test');
this.CACHE_PATH = path.resolve(this.TEST_PATH, 'cache');
this.LOGS_PATH = path.resolve(this.TEST_PATH, 'logs');
@ -41,7 +43,7 @@ module.exports = function () {
this.OSRM_PORT = process.env.OSRM_PORT && parseInt(process.env.OSRM_PORT) || 5000;
this.OSRM_IP = process.env.OSRM_IP || '127.0.0.1';
this.OSRM_CONNECTION_RETRIES = process.env.OSRM_CONNECTION_RETRIES && parseInt(process.env.OSRM_CONNECTION_RETRIES) || 10;
this.OSRM_CONNECTION_RETRIES = process.env.OSRM_CONNECTION_RETRIES && parseInt(process.env.OSRM_CONNECTION_RETRIES) || 100;
this.OSRM_CONNECTION_EXP_BACKOFF_COEF = process.env.OSRM_CONNECTION_EXP_BACKOFF_COEF && parseFloat(process.env.OSRM_CONNECTION_EXP_BACKOFF_COEF) || 1.0;
this.HOST = `http://${this.OSRM_IP}:${this.OSRM_PORT}`;

View File

@ -39,7 +39,11 @@ module.exports = function () {
this.runBin = (bin, options, env, callback) => {
let cmd = path.resolve(util.format('%s/%s%s', this.BIN_PATH, bin, this.EXE));
if (this.USE_ROUTED_JS && bin === 'osrm-routed') {
cmd = 'osrm-routed-js';
}
let opts = options.split(' ').filter((x) => { return x && x.length > 0; });
let log = fs.createWriteStream(this.scenarioLogFile, {'flags': 'a'});
log.write(util.format('*** running %s %s\n', cmd, options));
// we need to set a large maxbuffer here because we have long running processes like osrm-routed

View File

@ -115,4 +115,4 @@ Feature: Annotations
When I route I should get
| from | to | route | a:speed | a:distance | a:duration | a:nodes |
| a | c | abc,abc | 10:10 | 249.9876189:299.962882 | 25:30 | 1:2:3 |
| a | c | abc,abc | 10:10 | 249.9876189:299.962882 +- 1e-7 | 25:30 | 1:2:3 |

View File

@ -80,7 +80,7 @@ Feature: Testbot - Exclude flags
| a | c | |
| a | f | |
| f | d | fg,gd,gd |
@skip_on_routed_js
Scenario: Testbot - exclude with unsupported exclude combination
Given the query options
| exclude | TwoWords2 |
@ -88,7 +88,7 @@ Feature: Testbot - Exclude flags
When I route I should get
| from | to | status | message |
| a | d | 400 | Exclude flag combination is not supported. |
@skip_on_routed_js
Scenario: Testbot - exclude with invalid exclude class name
Given the query options
| exclude | foo |

View File

@ -32,6 +32,7 @@ Feature: Ways of loading data
Then stderr should be empty
And it should exit successfully
@skip_on_routed_js
Scenario: osrm-datastore - Fail if no shared memory blocks are loaded
When I run "osrm-datastore --spring-clean" with input "Y"
And I try to run "osrm-routed --shared-memory=1"

View File

@ -563,6 +563,7 @@ Feature: Basic Map Matching
| trace | timestamps | code |
| abcd | 0 1 62 63 | NoMatch |
@skip_on_routed_js
Scenario: Testbot - Map matching invalid waypoints
Given the node map
"""
@ -580,6 +581,7 @@ Feature: Basic Map Matching
| trace | code |
| abcd | InvalidOptions |
@skip_on_routed_js
Scenario: Matching fail with waypoints param missing start/end
Given the node map
"""
@ -788,7 +790,7 @@ Feature: Basic Map Matching
| 1234 | 1.000135,1,1.000225,1,1.000404,1,1.000449,1 | 1:2:0.4 | 1:2:0.4 | 3.4 |
| 4321 | 1.000449,1,1.000404,1,1.000225,1,1.000135,1 | 0.4:2:1 | 0.4:2:1 | 3.4 |
@match @testbot
@match @testbot @skip_on_routed_js
Scenario: Regression test - add source phantom properly (two phantoms on one edge)
Given the profile "testbot"
Given a grid size of 10 meters

View File

@ -570,7 +570,8 @@ Feature: Snapping at intersections
| a,f,k | ac,cf,cf,fj,kj,kj | 132.8s | 132.8 |
| k,f | ik,fi,fi | 54.3s | 54.3 |
| f,a | ef,ae,ae | 66.6s | 66.6 |
| k,f,a | kj,fj,fj,ef,ae,ae | 141.4s | 141.4 |
| k,f,a | kj,fj,fj,ef,ae,ae | 141.4s +- 1e-7 | 141.4 +- 1e-7 |
When I request a travel time matrix I should get
| | a | f | k |
@ -626,4 +627,4 @@ Feature: Snapping at intersections
| a,f,k | ad,df,df,fj,kj,kj | 105.6s | 105.6 |
| k,f | ik,fi,fi | 54.3s | 54.3 |
| f,a | ef,ae,ae | 66.6s | 66.6 |
| k,f,a | ik,fi,fi,ef,ae,ae | 120.9s | 120.9 |
| k,f,a | ik,fi,fi,ef,ae,ae | 120.9s +- 1e-7 | 120.9 +- 1e-7 |

View File

@ -18,7 +18,7 @@ Feature: Status messages
| from | to | route | status | message |
| a | b | ab,ab | 200 | |
| b | a | ab,ab | 200 | |
@skip_on_routed_js
Scenario: No route found
Given the node map
"""
@ -39,6 +39,7 @@ Feature: Status messages
| a | c | | 400 | Impossible route between points |
| b | d | | 400 | Impossible route between points |
@skip_on_routed_js
Scenario: Malformed requests
Given the node locations
| node | lat | lon |

View File

@ -5,6 +5,7 @@ Feature: Basic trip planning
Given the profile "testbot"
Given a grid size of 10 meters
@skip_on_routed_js
Scenario: Testbot - Trip: Invalid options (like was in test suite for a long time)
Given the node map
"""
@ -222,7 +223,7 @@ Feature: Basic trip planning
| waypoints | trips | roundtrip | durations |
| a,b,c,d,e,f,g,h,i,j,k,l | lkjihgfedcbal | true | 22 |
| a,b,c,d,e,f,g,h,i,j,k,l | cbakjihgfedl | false | 19 |
@skip_on_routed_js
Scenario: Testbot - Trip: Unroutable roundtrip with waypoints (less than 10)
Given the node map
"""
@ -240,7 +241,7 @@ Feature: Basic trip planning
| waypoints | status | message |
| a,b,c,d | NoTrips | No trip visiting all destinations possible. |
@skip_on_routed_js
Scenario: Testbot - Trip: Unroutable roundtrip with waypoints (more than 10)
Given the node map
"""
@ -278,6 +279,7 @@ Feature: Basic trip planning
| a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p | NoTrips | No trip visiting all destinations possible. |
# Test TFSE
@skip_on_routed_js
Scenario: Testbot - Trip: TFSE with errors
Given the node map
"""
@ -371,7 +373,7 @@ Feature: Basic trip planning
| waypoints | source | destination | roundtrip | trips |
| a,b,d,e,c | first | last | true | abedca |
@skip_on_routed_js
Scenario: Testbot - Trip: midway points in isolated roads should return no trips
Given the node map
"""

View File

@ -53,10 +53,10 @@ Feature: Weight tests
When I route I should get
| waypoints | route | distances | weights | times | a:distance | a:duration | a:weight | a:speed |
| s,t | abc,abc | 20m,0m | 2,0 | 2s,0s | 20.03462663 | 2 | 2 | 10 |
| t,s | abc,abc | 20m,0m | 2,0 | 2s,0s | 20.03462663 | 2 | 2 | 10 |
| s,e | abc,abc | 40m,0m | 3.9,0 | 3.9s,0s | 29.94063646:10.01731331 | 3:0.9 | 3:0.9 | 10:11.1 |
| e,s | abc,abc | 40m,0m | 3.9,0 | 3.9s,0s | 10.01731331:29.94063646 | 0.9:3 | 0.9:3 | 11.1:10 |
| s,t | abc,abc | 20m,0m | 2,0 | 2s,0s | 20.03462663 +- 1e-7 | 2 | 2 | 10 |
| t,s | abc,abc | 20m,0m | 2,0 | 2s,0s | 20.03462663 +- 1e-7 | 2 | 2 | 10 |
| s,e | abc,abc | 40m,0m | 3.9,0 | 3.9s,0s | 29.94063646:10.01731331 +- 1e-7 | 3:0.9 | 3:0.9 | 10:11.1 |
| e,s | abc,abc | 40m,0m | 3.9,0 | 3.9s,0s | 10.01731331:29.94063646 +- 1e-7 | 0.9:3 | 0.9:3 | 11.1:10 |
Scenario: Step weights -- way_function: fail if no weight or weight_per_meter property

View File

@ -0,0 +1,14 @@
Feature: Tile service
Background:
Given the profile "testbot"
Scenario: Smoke test
Given the origin 52.5212,13.3919
Given the node map
"""
a b
"""
And the ways
| nodes |
| ab |
When I request /tile/v1/testbot/tile(8800,5373,14).mvt
Then HTTP code should be 200

View File

@ -99,7 +99,6 @@ inline void ParseResult(const osrm::Status &result_status, osrm::json::Object &r
throw std::logic_error(code_iter->second.get<osrm::json::String>().value.c_str());
}
result.values.erase(code_iter);
const auto message_iter = result.values.find("message");
if (message_iter != end_iter)
{

8
routed-js/Format.js Normal file
View File

@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Format = void 0;
var Format;
(function (Format) {
Format["Json"] = "json";
Format["Flatbuffers"] = "flatbuffers";
})(Format = exports.Format || (exports.Format = {}));

4
routed-js/Format.ts Normal file
View File

@ -0,0 +1,4 @@
export enum Format {
Json = 'json',
Flatbuffers = 'flatbuffers'
}

View File

@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MatchServiceHandler = void 0;
const ServiceHandler_1 = require("./ServiceHandler");
class MatchServiceHandler extends ServiceHandler_1.ServiceHandler {
buildServiceOptions(options, query) {
if (query.timestamps) {
options.timestamps = query.timestamps;
}
if (query.waypoints) {
options.waypoints = query.waypoints;
}
if (query.gaps) {
options.gaps = query.gaps;
}
if (query.tidy) {
options.tidy = query.tidy;
}
return options;
}
async callOSRM(options) {
return this.osrm.match(options);
}
}
exports.MatchServiceHandler = MatchServiceHandler;

View File

@ -0,0 +1,27 @@
import { ServiceHandler } from './ServiceHandler';
export class MatchServiceHandler extends ServiceHandler {
protected buildServiceOptions(options: any, query: any): any {
if (query.timestamps) {
options.timestamps = query.timestamps;
}
if (query.waypoints) {
options.waypoints = query.waypoints;
}
if (query.gaps) {
options.gaps = query.gaps;
}
if (query.tidy) {
options.tidy = query.tidy;
}
return options;
}
protected async callOSRM(options: any): Promise<any> {
return this.osrm.match(options);
}
}

View File

@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NearestServiceHandler = void 0;
const ServiceHandler_1 = require("./ServiceHandler");
class NearestServiceHandler extends ServiceHandler_1.ServiceHandler {
buildServiceOptions(options, query) {
if (query.number !== undefined) {
options.number = query.number;
}
return options;
}
async callOSRM(options) {
return this.osrm.nearest(options);
}
}
exports.NearestServiceHandler = NearestServiceHandler;

View File

@ -0,0 +1,14 @@
import { ServiceHandler } from './ServiceHandler';
export class NearestServiceHandler extends ServiceHandler {
protected buildServiceOptions(options: any, query: any): any {
if (query.number !== undefined) {
options.number = query.number;
}
return options;
}
protected async callOSRM(options: any): Promise<any> {
return this.osrm.nearest(options);
}
}

33
routed-js/OSRMWrapper.js Normal file
View File

@ -0,0 +1,33 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OSRMWrapper = exports.version = void 0;
const util_1 = __importDefault(require("util"));
const OSRM = require('../lib/index.js');
exports.version = OSRM.version;
class OSRMWrapper {
constructor(osrmOptions) {
this.osrm = new OSRM(osrmOptions);
}
async tile(zxy) {
return util_1.default.promisify(this.osrm.tile.bind(this.osrm))(zxy);
}
async route(options) {
return util_1.default.promisify(this.osrm.route.bind(this.osrm))(options, { format: 'buffer' });
}
async nearest(options) {
return util_1.default.promisify(this.osrm.nearest.bind(this.osrm))(options, { format: 'buffer' });
}
async table(options) {
return util_1.default.promisify(this.osrm.table.bind(this.osrm))(options, { format: 'buffer' });
}
async trip(options) {
return util_1.default.promisify(this.osrm.trip.bind(this.osrm))(options, { format: 'buffer' });
}
async match(options) {
return util_1.default.promisify(this.osrm.match.bind(this.osrm))(options, { format: 'buffer' });
}
}
exports.OSRMWrapper = OSRMWrapper;

36
routed-js/OSRMWrapper.ts Normal file
View File

@ -0,0 +1,36 @@
import util from 'util';
const OSRM = require('../lib/index.js');
export const version = OSRM.version;
export class OSRMWrapper {
private readonly osrm: typeof OSRM;
constructor(osrmOptions: any) {
this.osrm = new OSRM(osrmOptions);
}
async tile(zxy: [number, number, number]): Promise<any> {
return util.promisify(this.osrm.tile.bind(this.osrm))(zxy);
}
async route(options: any): Promise<any> {
return util.promisify(this.osrm.route.bind(this.osrm))(options, {format: 'buffer'});
}
async nearest(options: any): Promise<any> {
return util.promisify(this.osrm.nearest.bind(this.osrm))(options, {format: 'buffer'});
}
async table(options: any): Promise<any> {
return util.promisify(this.osrm.table.bind(this.osrm))(options, {format: 'buffer'});
}
async trip(options: any): Promise<any> {
return util.promisify(this.osrm.trip.bind(this.osrm))(options, {format: 'buffer'});
}
async match(options: any): Promise<any> {
return util.promisify(this.osrm.match.bind(this.osrm))(options, {format: 'buffer'});
}
}

View File

@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouteServiceHandler = void 0;
const ServiceHandler_1 = require("./ServiceHandler");
class RouteServiceHandler extends ServiceHandler_1.ServiceHandler {
buildServiceOptions(options, query) {
if (query.alternatives) {
options.alternatives = query.alternatives;
}
if (query.approaches) {
options.approaches = query.approaches;
}
if (query.waypoints) {
options.waypoints = query.waypoints;
}
return options;
}
async callOSRM(options) {
return this.osrm.route(options);
}
}
exports.RouteServiceHandler = RouteServiceHandler;

View File

@ -0,0 +1,23 @@
import { ServiceHandler } from './ServiceHandler';
export class RouteServiceHandler extends ServiceHandler {
protected buildServiceOptions(options: any, query: any): any {
if (query.alternatives) {
options.alternatives = query.alternatives;
}
if (query.approaches) {
options.approaches = query.approaches;
}
if (query.waypoints) {
options.waypoints = query.waypoints;
}
return options;
}
protected async callOSRM(options: any): Promise<any> {
return this.osrm.route(options);
}
}

View File

@ -0,0 +1,74 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ServiceHandler = void 0;
class ServiceHandler {
constructor(osrm) {
this.osrm = osrm;
}
async handle(coordinates, query, format) {
const options = this.build(coordinates, query, format);
return this.callOSRM(options);
}
build(coordinates, query, format) {
const options = this.buildBaseOptions(coordinates, query, format);
return this.buildServiceOptions(options, query);
}
buildBaseOptions(coordinates, query, format) {
const options = {
coordinates: coordinates
};
this.handleCommonParams(query, options, format);
return options;
}
handleCommonParams(query, options, format) {
options.format = format;
if (query.overview) {
options.overview = query.overview;
}
if (query.geometries) {
options.geometries = query.geometries;
}
if (query.steps) {
options.steps = query.steps;
}
// TODO: annotations is per-service option
if (query.annotations) {
options.annotations = query.annotations;
}
if (query.exclude) {
options.exclude = query.exclude;
}
if (query.snapping) {
options.snapping = query.snapping;
}
if (query.radiuses) {
options.radiuses = query.radiuses.map((r) => {
if (r === 'unlimited') {
return null;
}
return r;
});
}
if (query.bearings) {
options.bearings = query.bearings.map((bearingWithRange) => {
if (bearingWithRange.length == 0) {
return null;
}
return bearingWithRange;
});
}
if (query.hints) {
options.hints = query.hints;
}
if (query.generate_hints) {
options.generate_hints = query.generate_hints;
}
if (query.skip_waypoints) {
options.skip_waypoints = query.skip_waypoints;
}
if (query.continue_straight) {
options.continue_straight = ['default', 'true'].includes(query.continue_straight);
}
}
}
exports.ServiceHandler = ServiceHandler;

View File

@ -0,0 +1,94 @@
import { Format } from './Format';
import { OSRMWrapper } from './OSRMWrapper';
export abstract class ServiceHandler {
public constructor(protected readonly osrm: OSRMWrapper) { }
public async handle(coordinates: [number, number][], query: any, format: Format): Promise<any> {
const options = this.build(coordinates, query, format);
return this.callOSRM(options);
}
private build(coordinates: [number, number][], query: any, format: Format): any {
const options = this.buildBaseOptions(coordinates, query, format);
return this.buildServiceOptions(options, query);
}
protected abstract buildServiceOptions(options: any, query: any): any;
protected abstract callOSRM(options: any): Promise<any>;
private buildBaseOptions(coordinates: [number, number][], query: any, format: Format): any {
const options: any = {
coordinates: coordinates
};
this.handleCommonParams(query, options, format);
return options;
}
private handleCommonParams(query: any, options: any, format: Format) {
options.format = format;
if (query.overview) {
options.overview = query.overview;
}
if (query.geometries) {
options.geometries = query.geometries;
}
if (query.steps) {
options.steps = query.steps;
}
// TODO: annotations is per-service option
if (query.annotations) {
options.annotations = query.annotations;
}
if (query.exclude) {
options.exclude = query.exclude;
}
if (query.snapping) {
options.snapping = query.snapping;
}
if (query.radiuses) {
options.radiuses = query.radiuses.map((r: string | 'unlimited') => {
if (r === 'unlimited') {
return null;
}
return r;
});
}
if (query.bearings) {
options.bearings = query.bearings.map((bearingWithRange: number[]) => {
if (bearingWithRange.length == 0) {
return null;
}
return bearingWithRange;
});
}
if (query.hints) {
options.hints = query.hints;
}
if (query.generate_hints) {
options.generate_hints = query.generate_hints;
}
if (query.skip_waypoints) {
options.skip_waypoints = query.skip_waypoints;
}
if (query.continue_straight) {
options.continue_straight = ['default', 'true'].includes(query.continue_straight);
}
}
}

View File

@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TableServiceHandler = void 0;
const ServiceHandler_1 = require("./ServiceHandler");
class TableServiceHandler extends ServiceHandler_1.ServiceHandler {
buildServiceOptions(options, query) {
if (query.scale_factor) {
options.scale_factor = query.scale_factor;
}
if (query.fallback_coordinate) {
options.fallback_coordinate = query.fallback_coordinate;
}
if (query.fallback_speed) {
options.fallback_speed = query.fallback_speed;
}
if (query.sources && query.sources !== 'all') {
options.sources = query.sources;
}
if (query.destinations && query.destinations !== 'all') {
options.destinations = query.destinations;
}
return options;
}
async callOSRM(options) {
return this.osrm.table(options);
}
}
exports.TableServiceHandler = TableServiceHandler;

View File

@ -0,0 +1,26 @@
import { ServiceHandler } from './ServiceHandler';
export class TableServiceHandler extends ServiceHandler {
protected buildServiceOptions(options: any, query: any): any {
if (query.scale_factor) {
options.scale_factor = query.scale_factor;
}
if (query.fallback_coordinate) {
options.fallback_coordinate = query.fallback_coordinate;
}
if (query.fallback_speed) {
options.fallback_speed = query.fallback_speed;
}
if (query.sources && query.sources !== 'all') {
options.sources = query.sources;
}
if (query.destinations && query.destinations !== 'all') {
options.destinations = query.destinations;
}
return options;
}
protected async callOSRM(options: any): Promise<any> {
return this.osrm.table(options);
}
}

View File

@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TripServiceHandler = void 0;
const ServiceHandler_1 = require("./ServiceHandler");
class TripServiceHandler extends ServiceHandler_1.ServiceHandler {
buildServiceOptions(options, query) {
if (query.roundtrip != null) {
options.roundtrip = query.roundtrip;
}
if (query.source) {
options.source = query.source;
}
if (query.destination) {
options.destination = query.destination;
}
return options;
}
async callOSRM(options) {
return this.osrm.trip(options);
}
}
exports.TripServiceHandler = TripServiceHandler;

View File

@ -0,0 +1,21 @@
import { ServiceHandler } from './ServiceHandler';
export class TripServiceHandler extends ServiceHandler {
protected buildServiceOptions(options: any, query: any): any {
if (query.roundtrip != null) {
options.roundtrip = query.roundtrip;
}
if (query.source) {
options.source = query.source;
}
if (query.destination) {
options.destination = query.destination;
}
return options;
}
protected async callOSRM(options: any): Promise<any> {
return this.osrm.trip(options);
}
}

2810
routed-js/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

44
routed-js/package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "@project-osrm/osrm-routed",
"version": "5.28.0-unreleased",
"private": false,
"description": "API for OSRM",
"dependencies": {
"@fastify/compress": "^6.1.1",
"fastify": "^4.9.2",
"mkdirp": "^0.5.6",
"nan": "^2.17.0",
"node-cmake": "^2.5.1",
"rimraf": "^2.7.1"
},
"browserify": {
"transform": [
"babelify",
"brfs"
]
},
"bin": {
"osrm-routed-js": "./routed.js"
},
"repository": {
"type": "git",
"url": "https://github.com/Project-OSRM/osrm-backend.git"
},
"author": "Project OSRM Team",
"license": "BSD-2-Clause",
"bugs": {
"url": "https://github.com/Project-OSRM/osrm-backend/issues"
},
"homepage": "https://github.com/Project-OSRM/osrm-backend",
"engines": {
"node": ">=4.0.0"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/node": "^18.11.3",
"@types/yargs": "^17.0.13",
"typescript": "^4.8.4"
}
}

168
routed-js/routed.js Executable file
View File

@ -0,0 +1,168 @@
#!/usr/bin/env node
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fastify_1 = __importDefault(require("fastify"));
const OSRMWrapper_1 = require("./OSRMWrapper");
const yargs_1 = __importDefault(require("yargs/yargs"));
const cluster_1 = __importDefault(require("cluster"));
const os_1 = __importDefault(require("os"));
const schema_1 = require("./schema");
const MatchServiceHandler_1 = require("./MatchServiceHandler");
const NearestServiceHandler_1 = require("./NearestServiceHandler");
const RouteServiceHandler_1 = require("./RouteServiceHandler");
const TableServiceHandler_1 = require("./TableServiceHandler");
const TripServiceHandler_1 = require("./TripServiceHandler");
const Format_1 = require("./Format");
async function main() {
const argv = await (0, yargs_1.default)(process.argv.slice(2)).options({
ip: { type: 'string', default: '0.0.0.0', alias: 'i' },
port: { type: 'number', default: 5000, alias: 'p' },
workers: { type: 'number', alias: ['t', 'threads'], default: os_1.default.cpus().length },
shared_memory: { type: 'boolean', alias: ['shared-memory', 's'] },
mmap: { type: 'boolean', default: false, alias: ['m'] },
algorithm: { choices: ['CH', 'CoreCH', 'MLD'], default: 'CH', alias: 'a' },
dataset_name: { type: 'string', alias: 'dataset-name' },
max_viaroute_size: { type: 'number', alias: 'max-viaroute-size', default: 500 },
max_trip_size: { type: 'number', alias: 'max-trip-size', default: 100 },
max_table_size: { type: 'number', alias: 'max-table-size', default: 100 },
max_matching_size: { type: 'number', alias: 'max-matching-size', default: 100 },
max_nearest_size: { type: 'number', alias: 'max-nearest-size', default: 100 },
max_alternatives: { type: 'number', alias: 'max-alternatives', default: 3 },
max_matching_radius: { type: 'number', alias: 'max-matching-radius', default: -1 },
version: { alias: 'v' }
})
.help('h')
.alias('h', 'help')
.strict()
.argv;
if (argv.version) {
process.stdout.write(`v${OSRMWrapper_1.version}\n`);
return;
}
if (argv._.length == 0 && !argv.shared_memory) {
// TODO: show usage
return;
}
const osrm = new OSRMWrapper_1.OSRMWrapper({
path: argv._[0],
dataset_name: argv.dataset_name,
algorithm: argv.algorithm,
shared_memory: argv.shared_memory,
mmap_memory: argv.mmap,
max_viaroute_size: argv.max_viaroute_size,
max_trip_size: argv.max_trip_size,
max_table_size: argv.max_table_size,
max_matching_size: argv.max_matching_size,
max_nearest_size: argv.max_nearest_size,
max_alternatives: argv.max_alternatives,
max_matching_radius: argv.max_matching_size
});
const fastify = (0, fastify_1.default)({
logger: true,
maxParamLength: Number.MAX_SAFE_INTEGER,
rewriteUrl: (req) => {
// https://github.com/fastify/fastify/issues/2487
return req.url.replace(/;/g, '%3B');
},
querystringParser: schema_1.parseQueryString
});
await fastify.register(Promise.resolve().then(() => __importStar(require('@fastify/compress'))));
async function processRequest(handler, request, reply) {
const { coordinatesAndFormat } = request.params;
const query = request.query;
try {
const { format, coordinates } = (0, schema_1.parseCoordinatesAndFormat)(coordinatesAndFormat);
switch (format) {
case Format_1.Format.Json:
reply.type('application/json').code(200);
break;
case Format_1.Format.Flatbuffers:
reply.type('application/x-flatbuffers;schema=osrm.engine.api.fbresult').code(200);
break;
}
return handler.handle(coordinates, query, format);
}
catch (e) {
reply.code(400);
// TODO: bindings do not return `message`, but put `code` into `message`
return {
code: e.message,
message: e.message
};
}
}
fastify.get('/route/v1/:profile/:coordinatesAndFormat', { schema: schema_1.routeSchema }, async (request, reply) => {
return processRequest(new RouteServiceHandler_1.RouteServiceHandler(osrm), request, reply);
});
fastify.get('/nearest/v1/:profile/:coordinatesAndFormat', { schema: schema_1.nearestSchema }, async (request, reply) => {
return processRequest(new NearestServiceHandler_1.NearestServiceHandler(osrm), request, reply);
});
fastify.get('/table/v1/:profile/:coordinatesAndFormat', { schema: schema_1.tableSchema }, async (request, reply) => {
return processRequest(new TableServiceHandler_1.TableServiceHandler(osrm), request, reply);
});
fastify.get('/match/v1/:profile/:coordinatesAndFormat', { schema: schema_1.matchSchema }, async (request, reply) => {
return processRequest(new MatchServiceHandler_1.MatchServiceHandler(osrm), request, reply);
});
fastify.get('/trip/v1/:profile/:coordinatesAndFormat', { schema: schema_1.tripSchema }, async (request, reply) => {
return processRequest(new TripServiceHandler_1.TripServiceHandler(osrm), request, reply);
});
fastify.get('/tile/v1/:profile/tile(:x,:y,:zoom).mvt', { schema: schema_1.tileSchema }, async (request, reply) => {
const { x, y, zoom } = request.params;
reply.type('application/x-protobuf').code(200);
return osrm.tile([zoom, x, y]);
});
const start = async () => {
try {
await fastify.listen({ port: argv.port, host: argv.ip });
process.stdout.write('running and waiting for requests\n');
}
catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
const clusterWorkerSize = argv.workers;
if (clusterWorkerSize > 1) {
if (cluster_1.default.isMaster) {
for (let i = 0; i < clusterWorkerSize; i++) {
cluster_1.default.fork();
}
cluster_1.default.on("exit", function (worker) {
console.log("Worker", worker.id, " has exited.");
});
}
else {
start();
}
}
else {
start();
}
}
main();

165
routed-js/routed.ts Executable file
View File

@ -0,0 +1,165 @@
#!/usr/bin/env node
import Fastify, { FastifyReply, FastifyRequest } from 'fastify';
import { OSRMWrapper, version as OSRMVersion } from './OSRMWrapper';
import yargs from 'yargs/yargs';
import cluster from 'cluster';
import os from 'os';
import { routeSchema, nearestSchema, tableSchema, tripSchema, matchSchema, tileSchema, parseQueryString, parseCoordinatesAndFormat } from './schema';
import { ServiceHandler } from './ServiceHandler';
import { MatchServiceHandler } from './MatchServiceHandler';
import { NearestServiceHandler } from './NearestServiceHandler';
import { RouteServiceHandler } from './RouteServiceHandler';
import { TableServiceHandler } from './TableServiceHandler';
import { TripServiceHandler } from './TripServiceHandler';
import { Format } from './Format';
async function main() {
const argv = await yargs(process.argv.slice(2)).options({
ip: { type: 'string', default: '0.0.0.0', alias: 'i' },
port: { type: 'number', default: 5000, alias: 'p' },
workers: { type: 'number', alias: ['t', 'threads'], default: os.cpus().length },
shared_memory: { type: 'boolean', alias: ['shared-memory', 's'] },
mmap: { type: 'boolean', default: false, alias: ['m'] },
algorithm: { choices: ['CH', 'CoreCH', 'MLD'], default: 'CH', alias: 'a' },
dataset_name: { type: 'string', alias: 'dataset-name' },
max_viaroute_size: { type: 'number', alias: 'max-viaroute-size', default: 500 },
max_trip_size: { type: 'number', alias: 'max-trip-size', default: 100 },
max_table_size: { type: 'number', alias: 'max-table-size', default: 100 },
max_matching_size: { type: 'number', alias: 'max-matching-size', default: 100 },
max_nearest_size: { type: 'number', alias: 'max-nearest-size', default: 100 },
max_alternatives: { type: 'number', alias: 'max-alternatives', default: 3 },
max_matching_radius: { type: 'number', alias: 'max-matching-radius', default: -1 },
version: { alias: 'v' }
})
.help('h')
.alias('h', 'help')
.strict()
.argv;
if (argv.version) {
process.stdout.write(`v${OSRMVersion}\n`);
return;
}
if (argv._.length == 0 && !argv.shared_memory) {
// TODO: show usage
return;
}
const osrm = new OSRMWrapper({
path: argv._[0],
dataset_name: argv.dataset_name,
algorithm: argv.algorithm,
shared_memory: argv.shared_memory,
mmap_memory: argv.mmap,
max_viaroute_size: argv.max_viaroute_size,
max_trip_size: argv.max_trip_size,
max_table_size: argv.max_table_size,
max_matching_size: argv.max_matching_size,
max_nearest_size: argv.max_nearest_size,
max_alternatives: argv.max_alternatives,
max_matching_radius: argv.max_matching_size
});
const fastify = Fastify({
logger: true,
maxParamLength: Number.MAX_SAFE_INTEGER,
rewriteUrl: (req) => {
// https://github.com/fastify/fastify/issues/2487
return req.url!.replace(/;/g, '%3B');
},
querystringParser: parseQueryString
});
await fastify.register(
import('@fastify/compress')
);
async function processRequest(handler: ServiceHandler, request: FastifyRequest, reply: FastifyReply) {
const { coordinatesAndFormat } = request.params as any;
const query = request.query as any;
try {
const { format, coordinates } = parseCoordinatesAndFormat(coordinatesAndFormat);
switch (format) {
case Format.Json:
reply.type('application/json').code(200);
break;
case Format.Flatbuffers:
reply.type('application/x-flatbuffers;schema=osrm.engine.api.fbresult').code(200);
break;
}
return handler.handle(coordinates, query, format);
} catch (e: any) {
reply.code(400);
// TODO: bindings do not return `message`, but put `code` into `message`
return {
code: e.message,
message: e.message
};
}
}
fastify.get('/route/v1/:profile/:coordinatesAndFormat', { schema: routeSchema }, async (request, reply) => {
return processRequest(new RouteServiceHandler(osrm), request, reply);
});
fastify.get('/nearest/v1/:profile/:coordinatesAndFormat', { schema: nearestSchema }, async (request, reply) => {
return processRequest(new NearestServiceHandler(osrm), request, reply);
});
fastify.get('/table/v1/:profile/:coordinatesAndFormat', { schema: tableSchema }, async (request, reply) => {
return processRequest(new TableServiceHandler(osrm), request, reply);
});
fastify.get('/match/v1/:profile/:coordinatesAndFormat', { schema: matchSchema }, async (request, reply) => {
return processRequest(new MatchServiceHandler(osrm), request, reply);
});
fastify.get('/trip/v1/:profile/:coordinatesAndFormat', { schema: tripSchema }, async (request, reply) => {
return processRequest(new TripServiceHandler(osrm), request, reply);
});
fastify.get('/tile/v1/:profile/tile(:x,:y,:zoom).mvt', { schema: tileSchema }, async (request, reply) => {
const { x, y, zoom } = request.params as any;
reply.type('application/x-protobuf').code(200);
return osrm.tile([zoom, x, y]);
});
const start = async () => {
try {
await fastify.listen({ port: argv.port, host: argv.ip });
process.stdout.write('running and waiting for requests\n');
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
const clusterWorkerSize = argv.workers;
if (clusterWorkerSize > 1) {
if (cluster.isMaster) {
for (let i=0; i < clusterWorkerSize; i++) {
cluster.fork();
}
cluster.on("exit", function(worker: any) {
console.log("Worker", worker.id, " has exited.")
})
} else {
start();
}
} else {
start();
}
}
main();

337
routed-js/schema.js Normal file
View File

@ -0,0 +1,337 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseCoordinatesAndFormat = exports.parseQueryString = exports.tileSchema = exports.tripSchema = exports.matchSchema = exports.tableSchema = exports.nearestSchema = exports.routeSchema = void 0;
const querystring_1 = __importDefault(require("querystring"));
const Format_1 = require("./Format");
const ajv_1 = __importDefault(require("ajv"));
function makeAnnotationsSchema(allowedAnnotations) {
return {
oneOf: [
{
type: 'array',
items: {
enum: allowedAnnotations
}
},
{
type: 'boolean'
}
],
default: false
};
}
const queryStringJsonSchemaGeneral = {
type: 'object',
properties: {
// TODO: check numbers of elements is the same in bearings and radiuses
bearings: {
type: 'array',
items: {
type: 'array',
// TODO: check [min;max]
items: {
type: 'number'
}
}
},
radiuses: {
type: 'array',
items: {
anyOf: [
{
type: 'number',
exclusiveMinimum: 0
},
{
type: 'string',
enum: ['unlimited']
}
]
}
},
generate_hints: { type: 'boolean', default: true },
hints: {
type: 'array',
items: {
type: 'string'
}
},
approaches: {
type: 'array',
items: {
// TODO: default?
enum: ['curb', 'unrestricted']
}
},
exclude: {
type: 'array',
items: {
type: 'string'
}
},
snapping: {
enum: ['any', 'default'],
default: 'default'
},
skip_waypoints: { type: 'boolean', default: false },
}
};
const queryStringJsonSchemaRoute = {
type: 'object',
properties: {
...queryStringJsonSchemaGeneral.properties,
// TODO: strict mode: use allowUnionTypes to allow union
alternatives: { type: ['boolean', 'integer'], default: false },
steps: { type: 'boolean', default: false },
annotations: makeAnnotationsSchema(['duration', 'nodes', 'distance', 'weight', 'datasources', 'speed']),
geometries: {
enum: ['polyline', 'polyline6', 'geojson'],
default: 'polyline'
},
overview: {
enum: ['simplified', 'full', 'false'],
default: 'simplified'
},
continue_straight: {
enum: ['default', 'true', 'false'],
default: 'default'
},
waypoints: {
type: 'array',
items: {
type: 'integer'
}
}
}
};
const queryStringJsonSchemaNearest = {
type: 'object',
properties: {
...queryStringJsonSchemaGeneral.properties,
number: { type: ['integer'], default: 1 }
}
};
const queryStringJsonSchemaTable = {
type: 'object',
properties: {
...queryStringJsonSchemaGeneral.properties,
sources: {
anyOf: [
{
type: 'array',
items: {
type: 'integer',
minimum: 0
}
},
{
type: 'string',
enum: ['all']
}
]
},
destinations: {
anyOf: [
{
type: 'array',
items: {
type: 'integer',
minimum: 0
}
},
{
type: 'string',
enum: ['all']
}
]
},
annotations: {
type: 'array',
items: {
enum: ['duration', 'distance'],
default: 'duration'
}
},
fallback_speed: {
type: 'number',
exclusiveMinimum: 0
},
fallback_coordinate: {
enum: ['input', 'snapped'],
default: 'input'
},
scale_factor: {
type: 'number',
exclusiveMinimum: 0
}
}
};
const queryStringJsonSchemaMatch = {
type: 'object',
properties: {
...queryStringJsonSchemaGeneral.properties,
steps: { type: 'boolean', default: false },
geometries: {
enum: ['polyline', 'polyline6', 'geojson'],
default: 'polyline'
},
overview: {
enum: ['simplified', 'full', 'false'],
default: 'simplified'
},
timestamps: {
type: 'array',
items: {
type: 'integer'
}
},
gaps: {
enum: ['split', 'ignore'],
default: 'split'
},
tidy: { type: 'boolean', default: false },
waypoints: {
type: 'array',
items: {
type: 'integer'
}
},
annotations: makeAnnotationsSchema(['duration', 'nodes', 'distance', 'weight', 'datasources', 'speed'])
}
};
const queryStringJsonSchemaTrip = {
type: 'object',
properties: {
...queryStringJsonSchemaGeneral.properties,
roundtrip: { type: 'boolean', default: true },
source: {
enum: ['any', 'first'],
default: 'any'
},
destination: {
enum: ['any', 'last'],
default: 'any'
},
annotations: makeAnnotationsSchema(['duration', 'nodes', 'distance', 'weight', 'datasources', 'speed']),
steps: { type: 'boolean', default: false },
geometries: {
enum: ['polyline', 'polyline6', 'geojson'],
default: 'polyline'
},
overview: {
enum: ['simplified', 'full', 'false'],
default: 'simplified'
}
}
};
const paramsJsonSchema = {
type: 'object',
properties: {
coordinatesAndFormat: {
type: 'string'
}
}
};
exports.routeSchema = {
querystring: queryStringJsonSchemaRoute,
params: paramsJsonSchema
};
exports.nearestSchema = {
querystring: queryStringJsonSchemaNearest,
params: paramsJsonSchema
};
exports.tableSchema = {
querystring: queryStringJsonSchemaTable,
params: paramsJsonSchema
};
exports.matchSchema = {
querystring: queryStringJsonSchemaMatch,
params: paramsJsonSchema
};
exports.tripSchema = {
querystring: queryStringJsonSchemaTrip,
params: paramsJsonSchema
};
const paramsJsonSchemaTile = {
type: 'object',
properties: {
z: { type: 'integer', minimum: 12, maximum: 22 },
x: { type: 'integer', minimum: 0 },
y: { type: 'integer', minimum: 0 },
},
};
exports.tileSchema = {
params: paramsJsonSchemaTile,
};
function parseArray(listString, separator) {
// `querystring` parses `foo=1&foo=2` as `{ foo: ['1', '2'] }`
if (Array.isArray(listString)) {
return listString;
}
return listString.split(separator);
}
function parseQueryString(queryString) {
const parsed = querystring_1.default.parse(queryString, '&', '=', {
// 0 means "infinity"
maxKeys: 0
});
for (const key of ['timestamps', 'radiuses', 'approaches', 'waypoints', 'hints']) {
if (key in parsed) {
parsed[key] = parseArray(parsed[key], ';');
}
}
for (const key of ['sources', 'destinations']) {
if (key in parsed && parsed[key] !== 'all') {
parsed[key] = parseArray(parsed[key], ';');
}
}
if ('exclude' in parsed) {
parsed['exclude'] = parseArray(parsed['exclude'], ',');
}
if ('bearings' in parsed) {
parsed['bearings'] = parseArray(parsed['bearings'], ';').map(bearingWithRange => parseArray(bearingWithRange, ',').filter(bearing => bearing !== ''));
}
if ('annotations' in parsed) {
if (!['true', 'false'].includes(parsed['annotations'])) {
parsed['annotations'] = parseArray(parsed['annotations'], ',');
}
}
return parsed;
}
exports.parseQueryString = parseQueryString;
const coordinatesSchema = new ajv_1.default({ allErrors: true, coerceTypes: true }).compile({
type: 'array',
items: {
type: 'array',
items: {
type: 'number',
// TODO: ranges
minimum: -180,
maximum: 180
},
minItems: 2,
maxItems: 2
},
minItems: 1
});
function parseCoordinatesAndFormat(coordinatesAndFormat) {
let format = Format_1.Format.Json;
// try to handle case when we have format(i.e. `.flatbuffers` or `.json`) at the end
const lastDotIndex = coordinatesAndFormat.lastIndexOf('.');
if (lastDotIndex > 0) {
const formatString = coordinatesAndFormat.slice(lastDotIndex);
if (formatString == '.flatbuffers' || formatString == '.json') {
coordinatesAndFormat = coordinatesAndFormat.slice(0, lastDotIndex);
format = formatString == '.flatbuffers' ? Format_1.Format.Flatbuffers : Format_1.Format.Json;
}
}
const coordinates = coordinatesAndFormat.split(';').map(c => c.split(','));
if (!coordinatesSchema(coordinates)) {
throw { message: 'Invalid coordinates', code: 'InvalidQuery' };
}
return { coordinates: coordinates, format };
}
exports.parseCoordinatesAndFormat = parseCoordinatesAndFormat;

360
routed-js/schema.ts Normal file
View File

@ -0,0 +1,360 @@
import querystring from 'querystring';
import { Format } from './Format';
import Ajv from 'ajv';
function makeAnnotationsSchema(allowedAnnotations: string[]): any {
return {
oneOf: [
{
type: 'array',
items: {
enum: allowedAnnotations
}
},
{
type: 'boolean'
}
],
default: false
};
}
const queryStringJsonSchemaGeneral = {
type: 'object',
properties: {
// TODO: check numbers of elements is the same in bearings and radiuses
bearings: {
type: 'array',
items: {
type: 'array',
// TODO: check [min;max]
items: {
type: 'number'
}
}
},
radiuses: {
type: 'array',
items: {
anyOf: [
{
type: 'number',
exclusiveMinimum: 0
},
{
type: 'string',
enum: ['unlimited']
}
]
}
},
generate_hints: { type: 'boolean', default: true },
hints: {
type: 'array',
items: {
type: 'string'
}
},
approaches: {
type: 'array',
items: {
// TODO: default?
enum: ['curb', 'unrestricted']
}
},
exclude: {
type: 'array',
items: {
type: 'string'
}
},
snapping: {
enum: ['any', 'default'],
default: 'default'
},
skip_waypoints: { type: 'boolean', default: false },
}
};
const queryStringJsonSchemaRoute = {
type: 'object',
properties: {
...queryStringJsonSchemaGeneral.properties,
// TODO: strict mode: use allowUnionTypes to allow union
alternatives: { type: ['boolean', 'integer'], default: false },
steps: { type: 'boolean', default: false },
annotations: makeAnnotationsSchema(['duration', 'nodes', 'distance', 'weight', 'datasources', 'speed']),
geometries: {
enum: ['polyline', 'polyline6', 'geojson'],
default: 'polyline'
},
overview: {
enum: ['simplified', 'full', 'false'],
default: 'simplified'
},
continue_straight: {
enum: ['default', 'true', 'false'],
default: 'default'
},
waypoints: {
type: 'array',
items: {
type: 'integer'
}
}
}
};
const queryStringJsonSchemaNearest = {
type: 'object',
properties: {
...queryStringJsonSchemaGeneral.properties,
number: { type: ['integer'], default: 1 }
}
};
const queryStringJsonSchemaTable = {
type: 'object',
properties: {
...queryStringJsonSchemaGeneral.properties,
sources: {
anyOf: [
{
type: 'array',
items: {
type: 'integer',
minimum: 0
}
},
{
type: 'string',
enum: ['all']
}
]
},
destinations: {
anyOf: [
{
type: 'array',
items: {
type: 'integer',
minimum: 0
}
},
{
type: 'string',
enum: ['all']
}
]
},
annotations: {
type: 'array',
items: {
enum: ['duration', 'distance'],
default: 'duration'
}
},
fallback_speed: {
type: 'number',
exclusiveMinimum: 0
},
fallback_coordinate: {
enum: ['input', 'snapped'],
default: 'input'
},
scale_factor: {
type: 'number',
exclusiveMinimum: 0
}
}
};
const queryStringJsonSchemaMatch = {
type: 'object',
properties: {
...queryStringJsonSchemaGeneral.properties,
steps: { type: 'boolean', default: false },
geometries: {
enum: ['polyline', 'polyline6', 'geojson'],
default: 'polyline'
},
overview: {
enum: ['simplified', 'full', 'false'],
default: 'simplified'
},
timestamps: {
type: 'array',
items: {
type: 'integer'
}
},
gaps: {
enum: ['split', 'ignore'],
default: 'split'
},
tidy: { type: 'boolean', default: false },
waypoints: {
type: 'array',
items: {
type: 'integer'
}
},
annotations: makeAnnotationsSchema(['duration', 'nodes', 'distance', 'weight', 'datasources', 'speed'])
}
};
const queryStringJsonSchemaTrip = {
type: 'object',
properties: {
...queryStringJsonSchemaGeneral.properties,
roundtrip: { type: 'boolean', default: true },
source: {
enum: ['any', 'first'],
default: 'any'
},
destination: {
enum: ['any', 'last'],
default: 'any'
},
annotations: makeAnnotationsSchema(['duration', 'nodes', 'distance', 'weight', 'datasources', 'speed']),
steps: { type: 'boolean', default: false },
geometries: {
enum: ['polyline', 'polyline6', 'geojson'],
default: 'polyline'
},
overview: {
enum: ['simplified', 'full', 'false'],
default: 'simplified'
}
}
};
const paramsJsonSchema = {
type: 'object',
properties: {
coordinatesAndFormat: {
type: 'string'
}
}
};
export const routeSchema = {
querystring: queryStringJsonSchemaRoute,
params: paramsJsonSchema
};
export const nearestSchema = {
querystring: queryStringJsonSchemaNearest,
params: paramsJsonSchema
};
export const tableSchema = {
querystring: queryStringJsonSchemaTable,
params: paramsJsonSchema
};
export const matchSchema = {
querystring: queryStringJsonSchemaMatch,
params: paramsJsonSchema
};
export const tripSchema = {
querystring: queryStringJsonSchemaTrip,
params: paramsJsonSchema
};
const paramsJsonSchemaTile = {
type: 'object',
properties: {
z: { type: 'integer', minimum: 12, maximum: 22 },
x: { type: 'integer', minimum: 0 },
y: { type: 'integer', minimum: 0 },
},
};
export const tileSchema = {
params: paramsJsonSchemaTile,
};
function parseArray(listString: string | string[], separator: string): string[] {
// `querystring` parses `foo=1&foo=2` as `{ foo: ['1', '2'] }`
if (Array.isArray(listString)) {
return listString;
}
return listString.split(separator);
}
export function parseQueryString(queryString: string): any {
const parsed: any = querystring.parse(queryString, '&', '=', {
// 0 means "infinity"
maxKeys: 0
});
for (const key of ['timestamps', 'radiuses', 'approaches', 'waypoints', 'hints']) {
if (key in parsed) {
parsed[key] = parseArray(parsed[key], ';');
}
}
for (const key of ['sources', 'destinations']) {
if (key in parsed && parsed[key] !== 'all') {
parsed[key] = parseArray(parsed[key], ';');
}
}
if ('exclude' in parsed) {
parsed['exclude'] = parseArray(parsed['exclude'], ',');
}
if ('bearings' in parsed) {
parsed['bearings'] = parseArray(parsed['bearings'], ';').map(bearingWithRange => parseArray(bearingWithRange, ',').filter(bearing => bearing !== ''));
}
if ('annotations' in parsed) {
if (!['true', 'false'].includes(parsed['annotations'])) {
parsed['annotations'] = parseArray(parsed['annotations'], ',');
}
}
return parsed;
}
const coordinatesSchema = new Ajv({ allErrors: true, coerceTypes: true }).compile({
type: 'array',
items: {
type: 'array',
items: {
type: 'number',
// TODO: ranges
minimum: -180,
maximum: 180
},
minItems: 2,
maxItems: 2
},
minItems: 1
});
export function parseCoordinatesAndFormat(coordinatesAndFormat: string): { coordinates: [number, number][], format: Format } {
let format: Format = Format.Json;
// try to handle case when we have format(i.e. `.flatbuffers` or `.json`) at the end
const lastDotIndex = coordinatesAndFormat.lastIndexOf('.');
if (lastDotIndex > 0) {
const formatString = coordinatesAndFormat.slice(lastDotIndex);
if (formatString == '.flatbuffers' || formatString == '.json') {
coordinatesAndFormat = coordinatesAndFormat.slice(0, lastDotIndex);
format = formatString == '.flatbuffers' ? Format.Flatbuffers : Format.Json;
}
}
const coordinates: any = coordinatesAndFormat.split(';').map(c => c.split(','));
if (!coordinatesSchema(coordinates)) {
throw {message: 'Invalid coordinates', code: 'InvalidQuery'};
}
return { coordinates: coordinates as [number, number][], format };
}

14
routed-js/tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"lib": ["es2020"],
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": [
"./*"
]
}

View File

@ -16,3 +16,17 @@ do
{ set +x; } 2>/dev/null
done
done
# we do not run `osrm-routed-js` tests on v12 since fastify doesn't support it
NODE_VERSION=$(node --version)
NODE_MAJOR_VERSION=$(echo $NODE_VERSION | cut -d. -f1)
if [[ "$NODE_MAJOR_VERSION" != "v12" ]]; then
echo "Running osrm-routed-js tests"
export OSRM_USE_ROUTED_JS=1
node ./node_modules/cucumber/bin/cucumber.js features/ -p verify_routed_js
node ./node_modules/cucumber/bin/cucumber.js features/ -p mld_routed_js
unset OSRM_USE_ROUTED_JS
else
echo "Skipping osrm-routed-js tests on Node.js ${NODE_VERSION}"
fi

21
scripts/run_cucumber_tests.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/sh
set -e
# we do not run `osrm-routed-js` tests on v12 since fastify doesn't support it
NODE_VERSION=$(node --version)
NODE_MAJOR_VERSION=$(echo $NODE_VERSION | cut -d. -f1)
if [[ "$NODE_MAJOR_VERSION" != "v12" ]]; then
echo "Running osrm-routed-js tests"
export OSRM_USE_ROUTED_JS=1
node ./node_modules/cucumber/bin/cucumber.js features/ -p verify_routed_js
node ./node_modules/cucumber/bin/cucumber.js features/ -p mld_routed_js
unset OSRM_USE_ROUTED_JS
else
echo "Skipping osrm-routed-js tests on Node.js ${NODE_VERSION}"
fi
node ./node_modules/cucumber/bin/cucumber.js features/ -p verify
node ./node_modules/cucumber/bin/cucumber.js features/ -p verify -m mmap
node ./node_modules/cucumber/bin/cucumber.js features/ -p mld
node ./node_modules/cucumber/bin/cucumber.js features/ -p mld -m mmap