This commit is contained in:
Daniel Patterson 2018-09-05 23:29:01 +00:00 committed by GitHub
commit 01c24a115b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 6410 additions and 721 deletions

View File

@ -2,6 +2,7 @@
Feature: Testbot - side bias Feature: Testbot - side bias
Scenario: Left-hand bias Scenario: Left-hand bias
Given the valhalla admin db "left"
Given the profile file "car" initialized with Given the profile file "car" initialized with
""" """
profile.properties.left_hand_driving = true profile.properties.left_hand_driving = true

View File

@ -2,6 +2,7 @@
Feature: Turn Lane Guidance Feature: Turn Lane Guidance
Background: Background:
Given the valhalla admin db "left"
Given the profile "car" Given the profile "car"
Given a grid size of 100 meters Given a grid size of 100 meters

View File

@ -2,6 +2,7 @@
Feature: Basic Roundabout Feature: Basic Roundabout
Background: Background:
Given the valhalla admin db "left"
Given a grid size of 10 meters Given a grid size of 10 meters
Given the profile file "car" initialized with Given the profile file "car" initialized with
""" """

View File

@ -6,6 +6,7 @@ const Timeout = require('node-timeout');
const tryConnect = require('../lib/try_connect'); const tryConnect = require('../lib/try_connect');
const errorReason = require('./utils').errorReason; const errorReason = require('./utils').errorReason;
const child_process = require('child_process');
class OSRMBaseLoader{ class OSRMBaseLoader{
constructor (scope) { constructor (scope) {
this.scope = scope; this.scope = scope;
@ -45,7 +46,7 @@ class OSRMBaseLoader{
var retryCount = 0; var retryCount = 0;
let retry = (err) => { let retry = (err) => {
if (err) { if (err) {
if (retryCount < 10) { if (retryCount < 1000) {
retryCount++; retryCount++;
setTimeout(() => { tryConnect(this.scope.OSRM_IP, this.scope.OSRM_PORT, retry); }, 10); setTimeout(() => { tryConnect(this.scope.OSRM_IP, this.scope.OSRM_PORT, retry); }, 10);
} else { } else {
@ -88,6 +89,33 @@ class OSRMDirectLoader extends OSRMBaseLoader {
} }
}; };
class ValhallaDirectLoader extends OSRMBaseLoader {
constructor (scope) {
super(scope);
}
load (inputFile, callback) {
this.inputFile = inputFile;
this.shutdown(() => {
this.launch(callback);
});
}
osrmUp (callback) {
if (this.osrmIsRunning()) return callback(new Error("osrm-routed already running!"));
var args = [this.inputFile, '1'];
this.child = child_process.execFile(`${process.env.VALHALLA_HOME}/valhalla_service`, args, this.scope.environment, (err) => {
if (err && err.signal !== 'SIGINT') {
this.child = null;
throw new Error(util.format('valhalla_service %s: %s', errorReason(err), err.cmd));
}
});
setTimeout/
callback();
}
};
class OSRMDatastoreLoader extends OSRMBaseLoader { class OSRMDatastoreLoader extends OSRMBaseLoader {
constructor (scope) { constructor (scope) {
super(scope); super(scope);
@ -135,6 +163,7 @@ class OSRMLoader {
this.scope = scope; this.scope = scope;
this.sharedLoader = new OSRMDatastoreLoader(this.scope); this.sharedLoader = new OSRMDatastoreLoader(this.scope);
this.directLoader = new OSRMDirectLoader(this.scope); this.directLoader = new OSRMDirectLoader(this.scope);
this.valhallaLoader = new ValhallaDirectLoader(this.scope);
this.method = scope.DEFAULT_LOAD_METHOD; this.method = scope.DEFAULT_LOAD_METHOD;
} }
@ -151,6 +180,9 @@ class OSRMLoader {
this.loader = this.directLoader; this.loader = this.directLoader;
this.directLoader.load(inputFile, callback); this.directLoader.load(inputFile, callback);
}); });
} else if (this.method === 'valhalla') {
this.loader = this.valhallaLoader;
this.valhallaLoader.load(inputFile, callback);
} else { } else {
callback(new Error('*** Unknown load method ' + method)); callback(new Error('*** Unknown load method ' + method));
} }

View File

@ -13,6 +13,11 @@ module.exports = function () {
callback(); callback();
}); });
this.Given(/^the valhalla admin db "(.*?)"$/, (args, callback) => {
this.vahallaAdminDB = this.expandOptions(args);
callback();
});
this.Given(/^the extract extra arguments "(.*?)"$/, (args, callback) => { this.Given(/^the extract extra arguments "(.*?)"$/, (args, callback) => {
this.extractArgs = this.expandOptions(args); this.extractArgs = this.expandOptions(args);
callback(); callback();
@ -309,6 +314,11 @@ module.exports = function () {
callback(); callback();
}); });
this.Given(/^data is loaded with valhalla$/, (callback) => {
this.osrmLoader.setLoadMethod('valhalla');
callback();
});
this.Given(/^the HTTP method "([^"]*)"$/, (method, callback) => { this.Given(/^the HTTP method "([^"]*)"$/, (method, callback) => {
this.httpMethod = method; this.httpMethod = method;
callback(); callback();

View File

@ -111,17 +111,17 @@ module.exports = function () {
// result is an object containing the calculated values for 'rate', 'status', // result is an object containing the calculated values for 'rate', 'status',
// 'time', 'distance', 'speed' and 'mode', for forwards and backwards routing, as well as // 'time', 'distance', 'speed' and 'mode', for forwards and backwards routing, as well as
// a bothw object that diffs forwards/backwards // a bothw object that diffs forwards/backwards
var testRoutabilityRow = (i, cb) => { var testRoutabilityRow = (rowIndex, cb) => {
var result = {}; var result = {};
var testDirection = (dir, callback) => { var testDirection = (dir, callback) => {
var a = new classes.Location(this.origin[0] + (1+this.WAY_SPACING*i) * this.zoom, this.origin[1]), var a = new classes.Location(this.origin[0] + (1+this.WAY_SPACING*rowIndex) * this.zoom, this.origin[1]),
b = new classes.Location(this.origin[0] + (3+this.WAY_SPACING*i) * this.zoom, this.origin[1]), b = new classes.Location(this.origin[0] + (3+this.WAY_SPACING*rowIndex) * this.zoom, this.origin[1]),
r = {}; r = {};
r.which = dir; r.which = dir;
this.requestRoute((dir === 'forw' ? [a, b] : [b, a]), [], [], this.queryParams, (err, res, body) => { this.requestRoute((dir === 'forw' ? [a, b] : [b, a]), [], [], this.queryParams, `${this.scenarioCacheFile}_${rowIndex}_request.txt`, (err, res, body) => {
if (err) return callback(err); if (err) return callback(err);
r.query = this.query; r.query = this.query;
@ -132,7 +132,7 @@ module.exports = function () {
r.route = this.wayList(r.json.routes[0]); r.route = this.wayList(r.json.routes[0]);
r.summary = r.json.routes[0].legs.map(l => l.summary).join(','); r.summary = r.json.routes[0].legs.map(l => l.summary).join(',');
if (r.route.split(',')[0] === util.format('w%d', i)) { if (r.route.split(',')[0] === util.format('w%d', rowIndex)) {
r.time = r.json.routes[0].duration; r.time = r.json.routes[0].duration;
r.distance = r.json.routes[0].distance; r.distance = r.json.routes[0].distance;
r.rate = Math.round(r.distance / r.json.routes[0].weight * 10) / 10.; r.rate = Math.round(r.distance / r.json.routes[0].weight * 10) / 10.;

View File

@ -101,8 +101,10 @@ module.exports = function() {
this.setupScenarioCache = (scenarioID) => { this.setupScenarioCache = (scenarioID) => {
this.scenarioCacheFile = this.getScenarioCacheFile(this.featureCacheDirectory, scenarioID); this.scenarioCacheFile = this.getScenarioCacheFile(this.featureCacheDirectory, scenarioID);
this.scenarioCacheFilePBF = this.getScenarioCacheFilePBF(this.featureCacheDirectory, scenarioID);
this.processedCacheFile = this.getProcessedCacheFile(this.featureProcessedCacheDirectory, scenarioID); this.processedCacheFile = this.getProcessedCacheFile(this.featureProcessedCacheDirectory, scenarioID);
this.inputCacheFile = this.getInputCacheFile(this.featureProcessedCacheDirectory, scenarioID); this.inputCacheFile = this.getInputCacheFile(this.featureProcessedCacheDirectory, scenarioID);
this.inputCacheFilePBF = this.getInputCacheFilePBF(this.featureProcessedCacheDirectory, scenarioID);
this.rasterCacheFile = this.getRasterCacheFile(this.featureProcessedCacheDirectory, scenarioID); this.rasterCacheFile = this.getRasterCacheFile(this.featureProcessedCacheDirectory, scenarioID);
this.speedsCacheFile = this.getSpeedsCacheFile(this.featureProcessedCacheDirectory, scenarioID); this.speedsCacheFile = this.getSpeedsCacheFile(this.featureProcessedCacheDirectory, scenarioID);
this.penaltiesCacheFile = this.getPenaltiesCacheFile(this.featureProcessedCacheDirectory, scenarioID); this.penaltiesCacheFile = this.getPenaltiesCacheFile(this.featureProcessedCacheDirectory, scenarioID);
@ -176,6 +178,11 @@ module.exports = function() {
return path.join(featureCacheDirectory, scenarioID) + '.osm'; return path.join(featureCacheDirectory, scenarioID) + '.osm';
}; };
// test/cache/{feature_path}/{feature_hash}/{scenario}.osm
this.getScenarioCacheFilePBF = (featureCacheDirectory, scenarioID) => {
return path.join(featureCacheDirectory, scenarioID) + '.pbf';
};
// test/cache/{feature_path}/{feature_hash}/{osrm_hash}/ // test/cache/{feature_path}/{feature_hash}/{osrm_hash}/
this.getFeatureProcessedCacheDirectory = (featureCacheDirectory, osrmHash) => { this.getFeatureProcessedCacheDirectory = (featureCacheDirectory, osrmHash) => {
return path.join(featureCacheDirectory, osrmHash); return path.join(featureCacheDirectory, osrmHash);
@ -191,6 +198,11 @@ module.exports = function() {
return path.join(featureProcessedCacheDirectory, scenarioID) + '.osm'; return path.join(featureProcessedCacheDirectory, scenarioID) + '.osm';
}; };
// test/cache/{feature_path}/{feature_hash}/{osrm_hash}/{scenario}.osm
this.getInputCacheFilePBF = (featureProcessedCacheDirectory, scenarioID) => {
return path.join(featureProcessedCacheDirectory, scenarioID) + '.pbf';
};
return this; return this;
}; };

View File

@ -10,6 +10,8 @@ const tableDiff = require('../lib/table_diff');
const ensureDecimal = require('../lib/utils').ensureDecimal; const ensureDecimal = require('../lib/utils').ensureDecimal;
const errorReason = require('../lib/utils').errorReason; const errorReason = require('../lib/utils').errorReason;
const child_process = require('child_process');
module.exports = function () { module.exports = function () {
this.setGridSize = (meters) => { this.setGridSize = (meters) => {
// the constant is calculated (with BigDecimal as: 1.0/(DEG_TO_RAD*EARTH_RADIUS_IN_METERS // the constant is calculated (with BigDecimal as: 1.0/(DEG_TO_RAD*EARTH_RADIUS_IN_METERS
@ -168,7 +170,21 @@ module.exports = function () {
if (exists) callback(); if (exists) callback();
else { else {
this.OSMDB.toXML((xml) => { this.OSMDB.toXML((xml) => {
fs.writeFile(this.scenarioCacheFile, xml, callback); var q = d3.queue(1);
q.defer(fs.writeFile, this.scenarioCacheFile, xml);
q.defer((xml, cb) => {
delete require.cache[require.resolve('osmtogeojson/parse_osmxml')];
var osmtogeojson = require('osmtogeojson');
var osmxmltojson = require('osmtogeojson/parse_osmxml');
var json = osmxmltojson.parseFromString(xml);
var geojson = osmtogeojson(json);
fs.writeFile(`${this.scenarioCacheFile}.geojson`, JSON.stringify(geojson), cb);
}, xml);
var params = ['cat',this.scenarioCacheFile,'-O','-o',this.scenarioCacheFilePBF];
q.defer(child_process.execFile,'osmium', params, {maxBuffer: 1024 * 1024 * 1000, env: {}});
q.awaitAll(callback);
}); });
} }
}); });
@ -178,7 +194,10 @@ module.exports = function () {
fs.exists(this.inputCacheFile, (exists) => { fs.exists(this.inputCacheFile, (exists) => {
if (exists) callback(); if (exists) callback();
else { else {
fs.link(this.scenarioCacheFile, this.inputCacheFile, callback); fs.link(this.scenarioCacheFile, this.inputCacheFile, (err) => {
if (err) callback(err);
fs.link(this.scenarioCacheFilePBF, this.inputCacheFilePBF, callback);
});
} }
}); });
}; };
@ -239,23 +258,101 @@ module.exports = function () {
}); });
}; };
this.valhallaBuildConfig = (p, callback) => {
let stamp = p.processedCacheFile + '.stamp_valhalla_config';
fs.exists(stamp, (exists) => {
if (exists) return callback();
// valhalla_build_config --mjolnir-tile-dir ${PWD}/valhalla_tiles
// --mjolnir-tile-extract ${PWD}/valhalla_tiles.tar
// --mjolnir-timezone ${PWD}/valhalla_tiles/timezones.sqlite
// --mjolnir-admin ${PWD}/valhalla_tiles/admins.sqlite > valhalla.json
var params = [];
if (p.vahallaAdminDB === 'left') {
params = [`--mjolnir-tile-dir`, `${p.inputCacheDir}/${p.scenarioID}_valhalla_tiles`,
`--mjolnir-admin`, `/data/valhalla/admins_left.sqlite`];
} else {
params = [`--mjolnir-tile-dir`, `${p.inputCacheDir}/${p.scenarioID}_valhalla_tiles`,
`--mjolnir-admin`, `/data/valhalla/admins_right.sqlite`];
}
child_process.execFile(`${process.env.VALHALLA_HOME}/scripts/valhalla_build_config`, params, {}, (error, stdout, stderr) => {
if (error) { throw error; }
// Disable heirarchy calculation - it's slow, and not needed for small tests.
var config = JSON.parse(stdout);
config.mjolnir.hierarchy = false;
fs.writeFile(`${p.inputCacheDir}/${p.scenarioID}_valhalla_config.json`, JSON.stringify(config), (err) => {
if (err) throw err;
fs.writeFile(stamp, 'ok', callback);
})
});
});
};
this.valhallaBuildTiles = (p, callback) => {
let stamp = p.processedCacheFile + '.stamp_valhalla_tiles';
fs.exists(stamp, (exists) => {
if (exists) return callback();
var params = [`-c`,`${p.inputCacheDir}/${p.scenarioID}_valhalla_config.json`,
`${p.inputCacheFilePBF}`];
child_process.execFile(`${process.env.VALHALLA_HOME}/valhalla_build_tiles`, params, {}, (error, stdout, stderr) => {
if (error) { throw error; }
console.log(stdout);
console.log(stderr);
fs.writeFile(stamp, 'ok', callback);
});
});
};
this.valhallaTarTiles = (p, callback) => {
let stamp = p.processedCacheFile + '.stamp_valhalla_tiles';
fs.exists(stamp, (exists) => {
if (exists) return callback();
var params = [`cf`,`${p.inputCacheDir}/${p.scenarioID}_valhalla_tiles.tar`,
`-C`, p.inputCacheDir, `${p.scenarioID}_valhalla_tiles`];
child_process.execFile('/usr/bin/tar', params, {}, (error, stdout, stderr) => {
if (error) { throw error; }
console.log(stdout);
console.log(stderr);
fs.writeFile(stamp, 'ok', callback);
});
});
}
this.extractContractPartitionAndCustomize = (callback) => { this.extractContractPartitionAndCustomize = (callback) => {
// a shallow copy of scenario parameters to avoid data inconsistency // a shallow copy of scenario parameters to avoid data inconsistency
// if a cucumber timeout occurs during deferred jobs // if a cucumber timeout occurs during deferred jobs
let p = {extractArgs: this.extractArgs, contractArgs: this.contractArgs, let p = {extractArgs: this.extractArgs, contractArgs: this.contractArgs,
partitionArgs: this.partitionArgs, customizeArgs: this.customizeArgs, partitionArgs: this.partitionArgs, customizeArgs: this.customizeArgs,
profileFile: this.profileFile, inputCacheFile: this.inputCacheFile, profileFile: this.profileFile, inputCacheFile: this.inputCacheFile,
processedCacheFile: this.processedCacheFile, environment: this.environment}; inputCacheFilePBF: this.inputCacheFilePBF, vahallaAdminDB: this.vahallaAdminDB,
processedCacheFile: this.processedCacheFile, environment: this.environment,
inputCacheDir: this.featureProcessedCacheDirectory,
scenarioID: this.scenarioID };
let queue = d3.queue(1); let queue = d3.queue(1);
queue.defer(this.extractData.bind(this), p); queue.defer(this.extractData.bind(this), p);
queue.defer(this.partitionData.bind(this), p); queue.defer(this.partitionData.bind(this), p);
queue.defer(this.contractData.bind(this), p); queue.defer(this.contractData.bind(this), p);
queue.defer(this.customizeData.bind(this), p); queue.defer(this.valhallaBuildConfig.bind(this), p);
queue.defer(this.valhallaBuildTiles.bind(this), p);
queue.defer(this.valhallaTarTiles.bind(this), p);
queue.awaitAll(callback); queue.awaitAll(callback);
}; };
this.writeAndLinkOSM = (callback) => { this.writeAndLinkOSM = (callback) => {
let queue = d3.queue(1); let queue = d3.queue(1);
let p = { profileFile: this.profileFile, inputCacheFile: this.inputCacheFile,
inputCacheFilePBF: this.inputCacheFilePBF,
processedCacheFile: this.processedCacheFile, environment: this.environment,
inputCacheDir: this.featureProcessedCacheDirectory,
scenarioID: this.scenarioID };
queue.defer(this.writeOSM.bind(this)); queue.defer(this.writeOSM.bind(this));
queue.defer(this.linkOSM.bind(this)); queue.defer(this.linkOSM.bind(this));
queue.awaitAll(callback); queue.awaitAll(callback);
@ -272,7 +369,8 @@ module.exports = function () {
let queue = d3.queue(1); let queue = d3.queue(1);
queue.defer(this.writeAndLinkOSM.bind(this)); queue.defer(this.writeAndLinkOSM.bind(this));
queue.defer(this.extractContractPartitionAndCustomize.bind(this)); queue.defer(this.extractContractPartitionAndCustomize.bind(this));
queue.defer(this.osrmLoader.load.bind(this.osrmLoader), this.processedCacheFile); //queue.defer(this.osrmLoader.load.bind(this.osrmLoader), this.processedCacheFile);
queue.defer(this.osrmLoader.load.bind(this.osrmLoader), `${this.featureProcessedCacheDirectory}/${this.scenarioID}_valhalla_config.json`);
queue.awaitAll(callback); queue.awaitAll(callback);
}; };

View File

@ -32,7 +32,7 @@ module.exports = function () {
this.DEFAULT_ENVIRONMENT = Object.assign({STXXLCFG: stxxl_config}, process.env); this.DEFAULT_ENVIRONMENT = Object.assign({STXXLCFG: stxxl_config}, process.env);
this.DEFAULT_PROFILE = 'bicycle'; this.DEFAULT_PROFILE = 'bicycle';
this.DEFAULT_INPUT_FORMAT = 'osm'; this.DEFAULT_INPUT_FORMAT = 'osm';
this.DEFAULT_LOAD_METHOD = 'datastore'; this.DEFAULT_LOAD_METHOD = process.env.OSRM_LOAD_METHOD && process.env.OSRM_LOAD_METHOD || 'datastore';
this.DEFAULT_ORIGIN = [1,1]; this.DEFAULT_ORIGIN = [1,1];
this.OSM_USER = 'osrm'; this.OSM_USER = 'osrm';
this.OSM_UID = 1; this.OSM_UID = 1;

View File

@ -25,15 +25,18 @@ module.exports = function () {
this.profile = this.OSRM_PROFILE || this.DEFAULT_PROFILE; this.profile = this.OSRM_PROFILE || this.DEFAULT_PROFILE;
this.profileFile = path.join(this.PROFILES_PATH, this.profile + '.lua'); this.profileFile = path.join(this.PROFILES_PATH, this.profile + '.lua');
this.setupFeatureCache(feature); this.setupFeatureCache(feature);
this.feature = feature;
callback(); callback();
}); });
this.Before((scenario, callback) => { this.Before((scenario, callback) => {
this.scenario = scenario;
this.osrmLoader.setLoadMethod(this.DEFAULT_LOAD_METHOD); this.osrmLoader.setLoadMethod(this.DEFAULT_LOAD_METHOD);
this.setGridSize(this.DEFAULT_GRID_SIZE); this.setGridSize(this.DEFAULT_GRID_SIZE);
this.setOrigin(this.DEFAULT_ORIGIN); this.setOrigin(this.DEFAULT_ORIGIN);
this.queryParams = {}; this.queryParams = {};
this.extractArgs = ''; this.extractArgs = '';
this.vahallaAdminDB = '';
this.contractArgs = ''; this.contractArgs = '';
this.partitionArgs = ''; this.partitionArgs = '';
this.customizeArgs = ''; this.customizeArgs = '';

View File

@ -1,5 +1,6 @@
var Timeout = require('node-timeout'); var Timeout = require('node-timeout');
var request = require('request'); var request = require('request');
var fs = require('fs');
module.exports = function () { module.exports = function () {
this.paramsToString = (params) => { this.paramsToString = (params) => {
@ -22,13 +23,20 @@ module.exports = function () {
// FIXME this needs to be simplified! // FIXME this needs to be simplified!
// - remove usage of node-timeout // - remove usage of node-timeout
// - replace with node's native timout mechanism // - replace with node's native timout mechanism
this.sendRequest = (baseUri, parameters, callback) => { this.sendRequest = (baseUri, parameters, type, logfile, callback) => {
var limit = Timeout(this.TIMEOUT, { err: { statusCode: 408 } }); var limit = Timeout(this.TIMEOUT, { err: { statusCode: 408 } });
var runRequest = (cb) => { var runRequest = (cb) => {
var params = this.paramsToString(parameters);
this.query = baseUri + (params.length ? '/' + params : '');
if (type === 'GET') {
var params = this.paramsToString(parameters);
console.log(params);
this.query = baseUri + (params.length ? ((params[0] != '?') ? '/' : '' + params) : '');
console.log(this.query);
if (logfile && typeof logfile !== 'function') fs.writeFileSync(logfile, `GET ${this.query}`);
request(this.query, (err, res, body) => { request(this.query, (err, res, body) => {
if (err && err.code === 'ECONNREFUSED') { if (err && err.code === 'ECONNREFUSED') {
return cb(new Error('*** osrm-routed is not running.')); return cb(new Error('*** osrm-routed is not running.'));
@ -38,6 +46,19 @@ module.exports = function () {
return cb(err, res, body); return cb(err, res, body);
}); });
} else {
this.query = baseUri;
if (logfile && typeof logfile !== 'function') fs.writeFileSync(logfile, `POST ${this.query}\n\n${JSON.stringify(parameters)}`);
request.post(this.query, {body: JSON.stringify(parameters)}, (err, res, body) => {
if (err && err.code === 'ECONNREFUSED') {
return cb(new Error('*** osrm-routed is not running.'));
} else if (err && err.statusCode === 408) {
return cb(new Error());
}
return cb(err, res, body);
});
}
}; };
runRequest(limit((err, res, body) => { runRequest(limit((err, res, body) => {

View File

@ -5,15 +5,15 @@ const request = require('request');
const ensureDecimal = require('../lib/utils').ensureDecimal; const ensureDecimal = require('../lib/utils').ensureDecimal;
module.exports = function () { module.exports = function () {
this.requestPath = (service, params, callback) => { this.requestPath = (service, params, logfile, callback) => {
var uri; var uri;
if (service == 'timestamp') { if (service == 'timestamp' || this.osrmLoader.method === 'valhalla') {
uri = [this.HOST, service].join('/'); uri = [this.HOST, service].join('/');
} else { } else {
uri = [this.HOST, service, 'v1', this.profile].join('/'); uri = [this.HOST, service, 'v1', this.profile].join('/');
} }
return this.sendRequest(uri, params, callback); return this.sendRequest(uri, params, 'GET', logfile, callback);
}; };
this.requestUrl = (path, callback) => { this.requestUrl = (path, callback) => {
@ -46,7 +46,7 @@ module.exports = function () {
return waypoints.map(w => [w.lon, w.lat].map(ensureDecimal).join(',')); return waypoints.map(w => [w.lon, w.lat].map(ensureDecimal).join(','));
}; };
this.requestRoute = (waypoints, bearings, approaches, userParams, callback) => { this.requestRoute = (waypoints, bearings, approaches, userParams, logfile, callback) => {
if (bearings.length && bearings.length !== waypoints.length) throw new Error('*** number of bearings does not equal the number of waypoints'); if (bearings.length && bearings.length !== waypoints.length) throw new Error('*** number of bearings does not equal the number of waypoints');
if (approaches.length && approaches.length !== waypoints.length) throw new Error('*** number of approaches does not equal the number of waypoints'); if (approaches.length && approaches.length !== waypoints.length) throw new Error('*** number of approaches does not equal the number of waypoints');
@ -60,6 +60,8 @@ module.exports = function () {
params.coordinates = encodedWaypoints; params.coordinates = encodedWaypoints;
console.log(params);
if (bearings.length) { if (bearings.length) {
params.bearings = bearings.map(b => { params.bearings = bearings.map(b => {
var bs = b.split(','); var bs = b.split(',');
@ -71,7 +73,29 @@ module.exports = function () {
if (approaches.length) { if (approaches.length) {
params.approaches = approaches.join(';'); params.approaches = approaches.join(';');
} }
return this.requestPath('route', params, callback);
if (this.osrmLoader.method === 'valhalla') {
var val_costing = 'auto';
if (this.profile === 'bicycle') {
val_costing = 'bicycle';
} else if (this.profile === 'foot') {
val_costing = 'pedestrian';
}
params = {
json: JSON.stringify({
locations: waypoints.map(w => {return{ lat: w.lat, lon: w.lon, node_snap_tolerance: 0};}),
costing: val_costing,
directions_options:{
units:'miles'}
}),
format: 'osrm'
};
}
return this.requestPath('route', params, logfile, callback);
}; };
this.requestNearest = (node, userParams, callback) => { this.requestNearest = (node, userParams, callback) => {
@ -166,6 +190,7 @@ module.exports = function () {
}; };
this.bearingList = (instructions) => { this.bearingList = (instructions) => {
return this.extractInstructionList(instructions, s => ('in' in s.intersections[0] ? this.reverseBearing(s.intersections[0].bearings[s.intersections[0].in]) : 0) return this.extractInstructionList(instructions, s => ('in' in s.intersections[0] ? this.reverseBearing(s.intersections[0].bearings[s.intersections[0].in]) : 0)
+ '->' + + '->' +
('out' in s.intersections[0] ? s.intersections[0].bearings[s.intersections[0].out] : 0)); ('out' in s.intersections[0] ? s.intersections[0].bearings[s.intersections[0].out] : 0));

View File

@ -2,6 +2,8 @@
var util = require('util'); var util = require('util');
var assert = require('assert'); var assert = require('assert');
var polyline = require('polyline');
var fs = require('fs');
module.exports = function () { module.exports = function () {
this.ShouldGetAResponse = () => { this.ShouldGetAResponse = () => {
@ -28,10 +30,11 @@ module.exports = function () {
if (e) return callback(e); if (e) return callback(e);
var headers = new Set(table.raw()[0]); var headers = new Set(table.raw()[0]);
var requestRow = (row, ri, cb) => { var requestRow = (row, rowIndex, cb) => {
var got; var got;
var afterRequest = (err, res, body) => { var afterRequest = (err, res, body) => {
console.log(body);
if (err) return cb(err); if (err) return cb(err);
if (body && body.length) { if (body && body.length) {
let destinations, exits, pronunciations, instructions, refs, bearings, turns, modes, times, classes, let destinations, exits, pronunciations, instructions, refs, bearings, turns, modes, times, classes,
@ -40,31 +43,80 @@ module.exports = function () {
let json = JSON.parse(body); let json = JSON.parse(body);
var hasRoute = false;
got.code = json.code; got.code = json.code;
hasRoute = json.code === 'Ok' || json.code;
let hasRoute = json.code === 'Ok'; var route;
if (hasRoute) { if (hasRoute) {
instructions = this.wayList(json.routes[0]); route = json.routes[0];
pronunciations = this.pronunciationList(json.routes[0]); instructions = this.wayList(route);
refs = this.refList(json.routes[0]); pronunciations = this.pronunciationList(route);
destinations = this.destinationsList(json.routes[0]); refs = this.refList(route);
exits = this.exitsList(json.routes[0]); destinations = this.destinationsList(route);
bearings = this.bearingList(json.routes[0]); exits = this.exitsList(route);
turns = this.turnList(json.routes[0]); bearings = this.bearingList(route);
intersections = this.intersectionList(json.routes[0]); turns = this.turnList(route);
modes = this.modeList(json.routes[0]); intersections = this.intersectionList(route);
driving_sides = this.drivingSideList(json.routes[0]); modes = this.modeList(route);
classes = this.classesList(json.routes[0]); driving_sides = this.drivingSideList(route);
times = this.timeList(json.routes[0]); classes = this.classesList(route);
distances = this.distanceList(json.routes[0]); times = this.timeList(route);
lanes = this.lanesList(json.routes[0]); distances = this.distanceList(route);
summary = this.summary(json.routes[0]); lanes = this.lanesList(route);
locations = this.locations(json.routes[0]); summary = this.summary(route);
annotation = this.annotationList(json.routes[0]); locations = this.locations(route);
weight_name = this.weightName(json.routes[0]); annotation = this.annotationList(route);
weights = this.weightList(json.routes[0]); weight_name = this.weightName(route);
approaches = this.approachList(json.routes[0]); weights = this.weightList(route);
approaches = this.approachList(route);
fs.writeFileSync(`${this.scenarioCacheFile}_${rowIndex}_response.json`,body);
var geojson = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: { type: 'startpoint' },
geometry: {
type: 'Point',
coordinates: [parseFloat(waypoints[0].lon),parseFloat(waypoints[0].lat)]
}
}
,
{
type: 'Feature',
properties: { type: 'endpoint' },
geometry: {
type: 'Point',
coordinates: [parseFloat(waypoints[1].lon), parseFloat(waypoints[1].lat)]
}
},
{
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: []
},
properties: {}
}
]
};
// OSRM route geometry
// TODO: Assume polyline5 for now
if (typeof route.geometry === 'string') {
if (this.osrmLoader.method === 'valhalla') {
geojson.features[2].geometry.coordinates = polyline.decode(route.geometry,6).map(c => c.reverse());
} else {
geojson.features[2].geometry.coordinates = polyline.decode(route.geometry).map(c => c.reverse());
}
} else {
geojson.features[2].geometry = route.geometry;
}
fs.writeFileSync(`${this.scenarioCacheFile}_${rowIndex}_shape.geojson`,JSON.stringify(geojson));
} }
if (headers.has('status')) { if (headers.has('status')) {
@ -99,9 +151,9 @@ module.exports = function () {
got.alternative = this.wayList(json.routes[1]); got.alternative = this.wayList(json.routes[1]);
} }
var distance = hasRoute && json.routes[0].distance, var distance = hasRoute && route.distance,
time = hasRoute && json.routes[0].duration, time = hasRoute && route.duration,
weight = hasRoute && json.routes[0].weight; weight = hasRoute && route.weight;
if (headers.has('distance')) { if (headers.has('distance')) {
if (row.distance.length) { if (row.distance.length) {
@ -200,6 +252,16 @@ module.exports = function () {
putValue('driving_side', driving_sides); putValue('driving_side', driving_sides);
} }
var resultdata = {
feature: this.feature.getName(),
scenario: this.scenario.getName(),
row: rowIndex,
expected: table.hashes()[rowIndex],
got: got
}
console.log(resultdata);
fs.writeFileSync(`${this.scenarioCacheFile}_${rowIndex}_results.json`,JSON.stringify(resultdata));
for (var key in row) { for (var key in row) {
if (this.FuzzyMatch.match(got[key], row[key])) { if (this.FuzzyMatch.match(got[key], row[key])) {
got[key] = row[key]; got[key] = row[key];
@ -257,7 +319,9 @@ module.exports = function () {
got.from = row.from; got.from = row.from;
got.to = row.to; got.to = row.to;
this.requestRoute(waypoints, bearings, approaches, params, afterRequest);
this.currentRowIndex = rowIndex;
this.requestRoute(waypoints, bearings, approaches, params, `${this.scenarioCacheFile}_${rowIndex}_request.txt`, afterRequest);
} else if (row.waypoints) { } else if (row.waypoints) {
row.waypoints.split(',').forEach((n) => { row.waypoints.split(',').forEach((n) => {
var node = this.findNodeByName(n.trim()); var node = this.findNodeByName(n.trim());
@ -265,7 +329,7 @@ module.exports = function () {
waypoints.push(node); waypoints.push(node);
}); });
got.waypoints = row.waypoints; got.waypoints = row.waypoints;
this.requestRoute(waypoints, bearings, approaches, params, afterRequest); this.requestRoute(waypoints, bearings, approaches, params, `${this.scenarioCacheFile}_${rowIndex}_request.txt`, afterRequest);
} else { } else {
return cb(new Error('*** no waypoints')); return cb(new Error('*** no waypoints'));
} }

6498
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,11 +4,15 @@
"private": false, "private": false,
"description": "The Open Source Routing Machine is a high performance routing engine written in C++14 designed to run on OpenStreetMap data.", "description": "The Open Source Routing Machine is a high performance routing engine written in C++14 designed to run on OpenStreetMap data.",
"dependencies": { "dependencies": {
"carto": "^0.18.2",
"mapnik": "^3.6.2",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"nan": "^2.6.2", "nan": "^2.6.2",
"node-cmake": "^2.3.2", "node-cmake": "^2.3.2",
"node-pre-gyp": "^0.6.36", "node-pre-gyp": "^0.6.36",
"rimraf": "^2.5.4" "osmtogeojson": "^3.0.0-beta.3",
"rimraf": "^2.5.4",
"xmldoc": "^1.1.0"
}, },
"browserify": { "browserify": {
"transform": [ "transform": [

243
scripts/tests2doc.js Normal file
View File

@ -0,0 +1,243 @@
"use strict"
const fs = require('fs');
const path = require('path');
const d3 = require('d3-queue');
var mapnik = require('mapnik');
mapnik.register_default_input_plugins();
mapnik.register_default_fonts()
//console.log(mapnik.fonts());
mapnik.registerFonts("/usr/share/fonts", { recurse: true });
const find = (dir) =>
fs.readdirSync(dir)
.reduce((files, file) =>
fs.statSync(path.join(dir, file)).isDirectory() ?
files.concat(find(path.join(dir, file))) :
files.concat(path.join(dir, file)),
[]);
function makemappng(basefile, routefile, callback) {
var stylesheet = `
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE Map[]>
<Map srs="+init=epsg:3857" buffer-size="8">
<Style name="testmap" filter-mode="all">
<Rule>
<Filter>[highway] = 'motorway'</Filter>
<LineSymbolizer stroke-width="12" stroke-linejoin="round" stroke-linecap="round" stroke="#bbbbbb" />
<LineSymbolizer stroke-width="10" stroke-linejoin="round" stroke-linecap="round" stroke="#aacc77" />
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='-10'>'name=' + [name]</TextSymbolizer>
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='10'>'highway=' + [highway]</TextSymbolizer>
</Rule>
<Rule>
<Filter>[highway] = 'motorway_link'</Filter>
<LineSymbolizer stroke-width="12" stroke-linejoin="round" stroke-linecap="round" stroke="#bbbbbb" />
<LineSymbolizer stroke-width="10" stroke-linejoin="round" stroke-linecap="round" stroke="#77aacc" />
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='-10'>'name=' + [name]</TextSymbolizer>
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='10'>'highway=' + [highway]</TextSymbolizer>
</Rule>
<Rule>
<Filter>[highway] = 'trunk'</Filter>
<LineSymbolizer stroke-width="12" stroke-linejoin="round" stroke-linecap="round" stroke="#bbbbbb" />
<LineSymbolizer stroke-width="10" stroke-linejoin="round" stroke-linecap="round" stroke="#ccaa77" />
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='-10'>'name=' + [name]</TextSymbolizer>
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='10'>'highway=' + [highway]</TextSymbolizer>
</Rule>
<Rule>
<Filter>[highway] = 'trunk_link'</Filter>
<LineSymbolizer stroke-width="12" stroke-linejoin="round" stroke-linecap="round" stroke="#bbbbbb" />
<LineSymbolizer stroke-width="10" stroke-linejoin="round" stroke-linecap="round" stroke="#aa77cc" />
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='-10'>'name=' + [name]</TextSymbolizer>
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='10'>'highway=' + [highway]</TextSymbolizer>
</Rule>
<Rule>
<Filter>[highway] = 'primary'</Filter>
<LineSymbolizer stroke-width="10" stroke-linejoin="round" stroke-linecap="round" stroke="#bbbbbb" />
<LineSymbolizer stroke-width="8" stroke-linejoin="round" stroke-linecap="round" stroke="#77ccaa" />
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='-8'>'name=' + [name]</TextSymbolizer>
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='8'>'highway=' + [highway]</TextSymbolizer>
</Rule>
<Rule>
<Filter>[highway] = 'primary_link'</Filter>
<LineSymbolizer stroke-width="10" stroke-linejoin="round" stroke-linecap="round" stroke="#bbbbbb" />
<LineSymbolizer stroke-width="8" stroke-linejoin="round" stroke-linecap="round" stroke="#aa77cc" />
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='-8'>'name=' + [name]</TextSymbolizer>
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='8'>'highway=' + [highway]</TextSymbolizer>
</Rule>
<Rule>
<Filter>[highway] = 'secondary' or [highway] = 'secondary_link' or [highway] = 'tertiary' or [highway] = 'tertiary_link' or [highway] = 'residential' or [highway] = 'service' or [highway] = 'living_street'</Filter>
<LineSymbolizer stroke-width="10" stroke-linejoin="round" stroke-linecap="round" stroke="#bbbbbb" />
<LineSymbolizer stroke-width="8" stroke-linejoin="round" stroke-linecap="round" stroke="#77bb77" />
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='-8'>'name=' + [name]</TextSymbolizer>
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='8'>'highway=' + [highway]</TextSymbolizer>
</Rule>
<Rule>
<Filter>[route] = 'ferry'</Filter>
<LineSymbolizer stroke-width="10" stroke-linejoin="round" stroke-linecap="round" stroke="#bbbbbb" />
<LineSymbolizer stroke-width="8" stroke-linejoin="round" stroke-linecap="round" stroke="#7777bb" />
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='-8'>'name=' + [name]</TextSymbolizer>
<TextSymbolizer face-name="DejaVu Sans Bold" size="10" fill="black" placement="line" allow-overlap="true" dy='8'>'route=' + [route]</TextSymbolizer>
</Rule>
</Style>
<Layer name="testmap" srs="+init=epsg:4326">
<StyleName>testmap</StyleName>
<Datasource>
<Parameter name="type">geojson</Parameter>
<Parameter name="file">${basefile}</Parameter>
</Datasource>
</Layer>
<Style name="testroute-line" filter-mode="all">
<Rule>
<LineSymbolizer stroke-width="5" stroke-linejoin="round" stroke-linecap="round" stroke="#000077" offset="6"/>
<LineSymbolizer stroke-width="3" stroke-linejoin="round" stroke-linecap="round" stroke="#0000ff" offset="6"/>
</Rule>
</Style>
<Style name="testroute-markers" filter-mode="first">
<Rule>
<Filter>[type] = 'startpoint'</Filter>
<MarkersSymbolizer fill="green" width="20" height="20" stroke="green" allow-overlap="true"/>
</Rule>
<Rule>
<Filter>[type] = 'endpoint'</Filter>
<MarkersSymbolizer fill="red" width="20" height="20" stroke="red" allow-overlap="true"/>
</Rule>
</Style>
<Layer name="testroute" srs="+init=epsg:4326">
<StyleName>testroute-line</StyleName>
<StyleName>testroute-markers</StyleName>
<Datasource>
<Parameter name="type">geojson</Parameter>
<Parameter name="file">${routefile}</Parameter>
</Datasource>
</Layer>
</Map>`;
var map = new mapnik.Map(400, 300);
map.fromStringSync(stylesheet,{strict:true});
map.zoomAll();
var extent = map.extent;
extent[0] = extent[0] - Math.abs(extent[0]) * 0.00005;
extent[1] = extent[1] - Math.abs(extent[1]) * 0.00005;
extent[2] = extent[2] + Math.abs(extent[2]) * 0.00005;
extent[3] = extent[3] + Math.abs(extent[3]) * 0.00005;
map.zoomToBox(extent);
var buffer = new mapnik.Image(400,300)
map.render(buffer, {}, (err,image) => {
callback(image.encodeSync('png'));
});
}
var report = "";
var toc = [];
find('test/cache').filter((f) => f.match(/[0-9]+_results.json$/)).forEach((f) => {
var files = f.match(/(.*?)_([0-9]+)_results.json$/,f);
var results = JSON.parse(fs.readFileSync(f));
// Generate map image
var imagefile = `${files[1]}_${files[2]}.png`.replace(/\//g,'_');
var png = makemappng(`${files[1]}.geojson`, `${files[1]}_${files[2]}_shape.geojson`, (png) => {
fs.writeFileSync(`report/${imagefile}`,png);
});
var osmfile = `${files[1]}`.replace(/\//g,'_');
var pbffile = `${files[1]}`.replace(/\//g,'_').replace(/.osm/,'.pbf');
var responsefile = `${files[1]}_${files[2]}_response.json`.replace(/\//g,'_');
var requestfile = `${files[1]}_${files[2]}_request.txt`.replace(/\//g,'_');
fs.writeFileSync(`report/${osmfile}`, fs.readFileSync(files[1]));
fs.writeFileSync(`report/${pbffile}`, fs.readFileSync(`${files[1].replace(/.osm$/,'.pbf')}`));
fs.writeFileSync(`report/${responsefile}`, fs.readFileSync(`${files[1]}_${files[2]}_response.json`));
fs.writeFileSync(`report/${requestfile}`, fs.readFileSync(`${files[1]}_${files[2]}_request.txt`));
toc.push({ title: `${results.feature} - ${results.scenario}`, link: imagefile });
if (typeof results.got.turns === 'object')
results.got.turns = results.got.turns.join(',');
if (typeof results.expected.turns === 'string')
results.expected.turns = results.expected.turns.replace(/slight /g,'').replace(/sharp /g, '');
if (typeof results.got.turns === 'string')
results.got.turns = results.got.turns.replace(/slight /g,'').replace(/sharp /g, '');
report += `<div class='scenario ${results.got.turns == results.expected.turns ? 'ok' : 'error'}'>
<h2><a href="${imagefile}">[link]</a> <a name="${imagefile}">${results.feature} - ${results.scenario}</a></h2>
<a href="${osmfile}">Download OSM XML file for this test</a><br/>
<a href="${pbffile}">Download OSM PBF file for this test</a><br/>
<a href="${requestfile}">Download request used for this test</a><br/>
<a href="${responsefile}">Download raw response for this test</a>
<table class="row">
<tr>
<td>
<img src="${imagefile}"/>
</td>
<td>
<table class="results">
<tr><th/>`;
Object.keys(results.expected).forEach((k) => {
report += `<th style="text-align: left">${k}</th>`;
});
report += `</tr>`;
report += `<tr><th style='text-align: right'>OSRM</th>`;
Object.keys(results.expected).forEach((k) => {
report += `<td style="text-align: left">${results.expected[k]}</td>`;
});
report += `</tr>`;
report += `<tr><th style='text-align: right'>Valhalla</th>`;
Object.keys(results.expected).forEach((k) => {
report += `<td style="text-align: left" class="${results.expected[k] === results.got[k] ? "ok" :"error" }">${results.got[k]}</td>`;
});
report += `</tr>
</table>
</td></tr></table>
</div>
<hr/>
`;
// Generate HTML table
});
console.log(`
<!DOCYPE html>
<html>
<head>
<style>
body { font-family: sans-serif; }
.results td { padding-left: 1em; font-family: monospace; }
.results th { padding-left: 1em; }
.scenario.error { background: #fbb; }
.error { color: red; }
</style>
<body>
Notes: OSRM "slight" and "sharp" indicators have been removed for comparison purposes.
<hr/>
`);
toc.forEach(r => {
console.log(`<a href='#${r.link}'>${r.title}</a><br/>`);
});
console.log(report);
console.log(`
</body>
</html>
`);