Implement NodeJS based server fully replicating osrm-routed
This commit is contained in:
@@ -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 = {}));
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum Format {
|
||||
Json = 'json',
|
||||
Flatbuffers = 'flatbuffers'
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
async nearest(options) {
|
||||
return util_1.default.promisify(this.osrm.nearest.bind(this.osrm))(options);
|
||||
}
|
||||
async table(options) {
|
||||
return util_1.default.promisify(this.osrm.table.bind(this.osrm))(options);
|
||||
}
|
||||
async trip(options) {
|
||||
return util_1.default.promisify(this.osrm.trip.bind(this.osrm))(options);
|
||||
}
|
||||
async match(options) {
|
||||
return util_1.default.promisify(this.osrm.match.bind(this.osrm))(options);
|
||||
}
|
||||
}
|
||||
exports.OSRMWrapper = OSRMWrapper;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
async nearest(options: any): Promise<any> {
|
||||
return util.promisify(this.osrm.nearest.bind(this.osrm))(options);
|
||||
}
|
||||
|
||||
async table(options: any): Promise<any> {
|
||||
return util.promisify(this.osrm.table.bind(this.osrm))(options);
|
||||
}
|
||||
|
||||
async trip(options: any): Promise<any> {
|
||||
return util.promisify(this.osrm.trip.bind(this.osrm))(options);
|
||||
}
|
||||
|
||||
async match(options: any): Promise<any> {
|
||||
return util.promisify(this.osrm.match.bind(this.osrm))(options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
"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;
|
||||
}
|
||||
// throw Error(JSON.stringify(options));
|
||||
return options;
|
||||
}
|
||||
async callOSRM(options) {
|
||||
return this.osrm.route(options);
|
||||
}
|
||||
}
|
||||
exports.RouteServiceHandler = RouteServiceHandler;
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
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;
|
||||
}
|
||||
// throw Error(JSON.stringify(options));
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
protected async callOSRM(options: any): Promise<any> {
|
||||
return this.osrm.route(options);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Generated
+2319
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@project-osrm/osrm-routed",
|
||||
"version": "5.28.0-unreleased",
|
||||
"private": false,
|
||||
"description": "API for OSRM",
|
||||
"dependencies": {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
Executable
+120
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
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 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' },
|
||||
threads: { type: 'number', alias: 't' },
|
||||
shared_memory: { type: 'boolean', alias: ['shared-memory', 's'] },
|
||||
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,
|
||||
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
|
||||
});
|
||||
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;
|
||||
}
|
||||
const result = await handler.handle(coordinates, query, format);
|
||||
result['code'] = 'Ok';
|
||||
return result;
|
||||
}
|
||||
catch (e) {
|
||||
reply.code(400);
|
||||
return {
|
||||
code: e.code,
|
||||
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]);
|
||||
});
|
||||
fastify.listen({ port: argv.port, host: argv.ip }, (err, address) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
process.stdout.write('running and waiting for requests\n');
|
||||
});
|
||||
}
|
||||
main();
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env node
|
||||
import Fastify, { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { OSRMWrapper, version as OSRMVersion } from './OSRMWrapper';
|
||||
import yargs from 'yargs/yargs';
|
||||
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' },
|
||||
threads: { type: 'number', alias: 't' },
|
||||
shared_memory: { type: 'boolean', alias: ['shared-memory', 's'] },
|
||||
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,
|
||||
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
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const result = await handler.handle(coordinates, query, format);
|
||||
result['code'] = 'Ok';
|
||||
return result;
|
||||
} catch (e: any) {
|
||||
reply.code(400);
|
||||
|
||||
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]);
|
||||
});
|
||||
|
||||
|
||||
fastify.listen({ port: argv.port, host: argv.ip }, (err, address) => {
|
||||
if (err) { throw err; }
|
||||
|
||||
process.stdout.write('running and waiting for requests\n');
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
"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: {
|
||||
type: ['number', 'string'],
|
||||
oneOf: [
|
||||
{ exclusiveMinimum: 0 },
|
||||
{ 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
|
||||
});
|
||||
// TODO: copy-paste
|
||||
if ('timestamps' in parsed) {
|
||||
parsed['timestamps'] = parseArray(parsed['timestamps'], ';');
|
||||
}
|
||||
if ('approaches' in parsed) {
|
||||
parsed['approaches'] = parseArray(parsed['approaches'], ';');
|
||||
}
|
||||
if ('waypoints' in parsed) {
|
||||
parsed['waypoints'] = parseArray(parsed['waypoints'], ';');
|
||||
}
|
||||
if ('sources' in parsed && parsed['sources'] !== 'all') {
|
||||
parsed['sources'] = parseArray(parsed['sources'], ';');
|
||||
}
|
||||
if ('destinations' in parsed && parsed['destinations'] !== 'all') {
|
||||
parsed['destinations'] = parseArray(parsed['destinations'], ';');
|
||||
}
|
||||
if ('hints' in parsed) {
|
||||
parsed['hints'] = parseArray(parsed['hints'], ';');
|
||||
}
|
||||
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;
|
||||
@@ -0,0 +1,364 @@
|
||||
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: {
|
||||
type: ['number', 'string'],
|
||||
oneOf: [
|
||||
{ exclusiveMinimum: 0 },
|
||||
{ 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
|
||||
});
|
||||
// TODO: copy-paste
|
||||
if ('timestamps' in parsed) {
|
||||
parsed['timestamps'] = parseArray(parsed['timestamps'], ';');
|
||||
}
|
||||
if ('approaches' in parsed) {
|
||||
parsed['approaches'] = parseArray(parsed['approaches'], ';');
|
||||
}
|
||||
if ('waypoints' in parsed) {
|
||||
parsed['waypoints'] = parseArray(parsed['waypoints'], ';');
|
||||
}
|
||||
if ('sources' in parsed && parsed['sources'] !== 'all') {
|
||||
parsed['sources'] = parseArray(parsed['sources'], ';');
|
||||
}
|
||||
if ('destinations' in parsed && parsed['destinations'] !== 'all') {
|
||||
parsed['destinations'] = parseArray(parsed['destinations'], ';');
|
||||
}
|
||||
if ('hints' in parsed) {
|
||||
parsed['hints'] = parseArray(parsed['hints'], ';');
|
||||
}
|
||||
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 };
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2020"],
|
||||
"target": "es2020",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"./*"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user