Unpack paths and return total distance in matrix plugin for CH (#4990)

This commit is contained in:
Kajari Ghosh 2018-04-20 18:18:55 -04:00 committed by GitHub
parent 9970b7d580
commit 14860b62e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1886 additions and 538 deletions

View File

@ -1,5 +1,8 @@
# UNRELEASED
- Changes from 5.17.0:
- Features:
- ADDED: `table` plugin now optionally returns `distance` matrix as part of response [#4990](https://github.com/Project-OSRM/osrm-backend/pull/4990)
- ADDED: New optional parameter `annotations` for `table` that accepts `distance`, `duration`, or both `distance,duration` as values [#4990](https://github.com/Project-OSRM/osrm-backend/pull/4990)
- Infrastructure:
- ADDED: Updated libosmium and added protozero and vtzero libraries [#5037](https://github.com/Project-OSRM/osrm-backend/pull/5037)

View File

@ -9,7 +9,7 @@ High performance routing engine written in C++14 designed to run on OpenStreetMa
The following services are available via HTTP API, C++ library interface and NodeJs wrapper:
- Nearest - Snaps coordinates to the street network and returns the nearest matches
- Route - Finds the fastest route between coordinates
- Table - Computes the duration of the fastest route between all pairs of supplied coordinates
- Table - Computes the duration or distances of the fastest route between all pairs of supplied coordinates
- Match - Snaps noisy GPS traces to the road network in the most plausible way
- Trip - Solves the Traveling Salesman Problem using a greedy heuristic
- Tile - Generates Mapbox Vector Tiles with internal routing metadata

View File

@ -3,5 +3,5 @@ module.exports = {
verify: '--strict --tags ~@stress --tags ~@todo --tags ~@mld-only -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 --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'
};

View File

@ -222,13 +222,13 @@ curl 'http://router.project-osrm.org/route/v1/driving/13.388860,52.517037;13.397
### Table service
Computes the duration of the fastest route between all pairs of supplied coordinates.
Computes the duration of the fastest route between all pairs of supplied coordinates. Optionally, also returns the distances between the coordinate pairs. Note that the distances are not the shortest distance between two coordinates, but rather the distances of the fastest routes.
```endpoint
GET /table/v1/{profile}/{coordinates}?{sources}=[{elem}...];&destinations=[{elem}...]
GET /table/v1/{profile}/{coordinates}?{sources}=[{elem}...];&{destinations}=[{elem}...]&annotations={duration|distance|duration,distance}
```
**Coordinates**
**Options**
In addition to the [general options](#general-options) the following options are supported for this service:
@ -236,6 +236,7 @@ In addition to the [general options](#general-options) the following options are
|------------|--------------------------------------------------|---------------------------------------------|
|sources |`{index};{index}[;{index} ...]` or `all` (default)|Use location with given index as source. |
|destinations|`{index};{index}[;{index} ...]` or `all` (default)|Use location with given index as destination.|
|annotations |`duration` (default), `distance`, or `duration,distance`|Return additional table with distances to the response. Whether requested or not, the duration table is always returned.|
Unlike other array encoded options, the length of `sources` and `destinations` can be **smaller or equal**
to number of input locations;
@ -253,14 +254,23 @@ sources=0;5;7&destinations=5;1;4;2;3;6
#### Example Request
```curl
# Returns a 3x3 matrix:
# Returns a 3x3 duration matrix:
curl 'http://router.project-osrm.org/table/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219'
# Returns a 1x3 matrix
# Returns a 1x3 duration matrix
curl 'http://router.project-osrm.org/table/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219?sources=0'
# Returns a asymmetric 3x2 matrix with from the polyline encoded locations `qikdcB}~dpXkkHz`:
# Returns a asymmetric 3x2 duration matrix with from the polyline encoded locations `qikdcB}~dpXkkHz`:
curl 'http://router.project-osrm.org/table/v1/driving/polyline(egs_Iq_aqAppHzbHulFzeMe`EuvKpnCglA)?sources=0;1;3&destinations=2;4'
# Returns a 3x3 duration matrix:
curl 'http://router.project-osrm.org/table/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219&annotations=duration'
# Returns a 3x3 distance matrix:
curl 'http://router.project-osrm.org/table/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219&annotations=distance'
# Returns a 3x3 duration matrix and a 3x3 distance matrix:
curl 'http://router.project-osrm.org/table/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219&annotations=distance,duration'
```
**Response**
@ -268,17 +278,114 @@ curl 'http://router.project-osrm.org/table/v1/driving/polyline(egs_Iq_aqAppHzbHu
- `code` if the request was successful `Ok` otherwise see the service dependent and general status codes.
- `durations` array of arrays that stores the matrix in row-major order. `durations[i][j]` gives the travel time from
the i-th waypoint to the j-th waypoint. Values are given in seconds. Can be `null` if no route between `i` and `j` can be found.
- `distances` array of arrays that stores the matrix in row-major order. `distances[i][j]` gives the travel distance from
the i-th waypoint to the j-th waypoint. Values are given in meters. Can be `null` if no route between `i` and `j` can be found.
- `sources` array of `Waypoint` objects describing all sources in order
- `destinations` array of `Waypoint` objects describing all destinations in order
In case of error the following `code`s are supported in addition to the general ones:
| Type | Description |
|-------------------|-----------------|
| Type | Description |
|------------------|-----------------|
| `NoTable` | No route found. |
All other properties might be undefined.
#### Example Response
```json
{
"sources": [
{
"location": [
13.3888,
52.517033
],
"hint": "PAMAgEVJAoAUAAAAIAAAAAcAAAAAAAAArss0Qa7LNEHiVIRA4lSEQAoAAAAQAAAABAAAAAAAAADMAAAAAEzMAKlYIQM8TMwArVghAwEA3wps52D3",
"name": "Friedrichstraße"
},
{
"location": [
13.397631,
52.529432
],
"hint": "WIQBgL6mAoAEAAAABgAAAAAAAAA7AAAAhU6PQHvHj0IAAAAAQbyYQgQAAAAGAAAAAAAAADsAAADMAAAAf27MABiJIQOCbswA_4ghAwAAXwVs52D3",
"name": "Torstraße"
},
{
"location": [
13.428554,
52.523239
],
"hint": "7UcAgP___38fAAAAUQAAACYAAABTAAAAhSQKQrXq5kKRbiZCWJo_Qx8AAABRAAAAJgAAAFMAAADMAAAASufMAOdwIQNL58wA03AhAwMAvxBs52D3",
"name": "Platz der Vereinten Nationen"
}
],
"durations": [
[
0,
192.6,
382.8
],
[
199,
0,
283.9
],
[
344.7,
222.3,
0
]
],
"destinations": [
{
"location": [
13.3888,
52.517033
],
"hint": "PAMAgEVJAoAUAAAAIAAAAAcAAAAAAAAArss0Qa7LNEHiVIRA4lSEQAoAAAAQAAAABAAAAAAAAADMAAAAAEzMAKlYIQM8TMwArVghAwEA3wps52D3",
"name": "Friedrichstraße"
},
{
"location": [
13.397631,
52.529432
],
"hint": "WIQBgL6mAoAEAAAABgAAAAAAAAA7AAAAhU6PQHvHj0IAAAAAQbyYQgQAAAAGAAAAAAAAADsAAADMAAAAf27MABiJIQOCbswA_4ghAwAAXwVs52D3",
"name": "Torstraße"
},
{
"location": [
13.428554,
52.523239
],
"hint": "7UcAgP___38fAAAAUQAAACYAAABTAAAAhSQKQrXq5kKRbiZCWJo_Qx8AAABRAAAAJgAAAFMAAADMAAAASufMAOdwIQNL58wA03AhAwMAvxBs52D3",
"name": "Platz der Vereinten Nationen"
}
],
"code": "Ok",
"distances": [
[
0,
1886.89,
3791.3
],
[
1824,
0,
2838.09
],
[
3275.36,
2361.73,
0
]
]
}
```
### Match service
Map matching matches/snaps given GPS points to the road network in the most plausible way.

View File

@ -110,8 +110,8 @@ Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refer
### table
Computes duration tables for the given locations. Allows for both symmetric and asymmetric
tables.
Computes duration table for the given locations. Allows for both symmetric and asymmetric
tables. Optionally returns distance table.
**Parameters**
@ -126,6 +126,7 @@ tables.
location with given index as source. Default is to use all.
- `options.destinations` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)?** An array of `index` elements (`0 <= integer <
#coordinates`) to use location with given index as destination. Default is to use all.
- `options.annotations` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)?** An array of the table types to return. Values can be `duration` or `distance` or both. Default is to return only duration table.
- `options.approaches` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)?** Keep waypoints on curb side. Can be `null` (unrestricted, default) or `curb`.
- `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)**
@ -142,6 +143,7 @@ var options = {
};
osrm.table(options, function(err, response) {
console.log(response.durations); // array of arrays, matrix in row-major order
console.log(response.distances); // array of arrays, matrix in row-major order
console.log(response.sources); // array of Waypoint objects
console.log(response.destinations); // array of Waypoint objects
});

View File

@ -1,74 +1,88 @@
var util = require('util');
module.exports = function () {
this.When(/^I request a travel time matrix I should get$/, (table, callback) => {
var NO_ROUTE = 2147483647; // MAX_INT
const durationsRegex = new RegExp(/^I request a travel time matrix I should get$/);
const distancesRegex = new RegExp(/^I request a travel distance matrix I should get$/);
var tableRows = table.raw();
const DURATIONS_NO_ROUTE = 2147483647; // MAX_INT
const DISTANCES_NO_ROUTE = 3.40282e+38; // MAX_FLOAT
if (tableRows[0][0] !== '') throw new Error('*** Top-left cell of matrix table must be empty');
this.When(durationsRegex, function(table, callback) {tableParse.call(this, table, DURATIONS_NO_ROUTE, 'durations', callback);}.bind(this));
this.When(distancesRegex, function(table, callback) {tableParse.call(this, table, DISTANCES_NO_ROUTE, 'distances', callback);}.bind(this));
};
var waypoints = [],
columnHeaders = tableRows[0].slice(1),
rowHeaders = tableRows.map((h) => h[0]).slice(1),
symmetric = columnHeaders.length == rowHeaders.length && columnHeaders.every((ele, i) => ele === rowHeaders[i]);
const durationsParse = function(v) { return isNaN(parseInt(v)); };
const distancesParse = function(v) { return isNaN(parseFloat(v)); };
if (symmetric) {
columnHeaders.forEach((nodeName) => {
var node = this.findNodeByName(nodeName);
if (!node) throw new Error(util.format('*** unknown node "%s"', nodeName));
waypoints.push({ coord: node, type: 'loc' });
function tableParse(table, noRoute, annotation, callback) {
const parse = annotation == 'distances' ? distancesParse : durationsParse;
const params = this.queryParams;
params.annotations = annotation == 'distances' ? 'distance' : 'duration';
var tableRows = table.raw();
if (tableRows[0][0] !== '') throw new Error('*** Top-left cell of matrix table must be empty');
var waypoints = [],
columnHeaders = tableRows[0].slice(1),
rowHeaders = tableRows.map((h) => h[0]).slice(1),
symmetric = columnHeaders.length == rowHeaders.length && columnHeaders.every((ele, i) => ele === rowHeaders[i]);
if (symmetric) {
columnHeaders.forEach((nodeName) => {
var node = this.findNodeByName(nodeName);
if (!node) throw new Error(util.format('*** unknown node "%s"', nodeName));
waypoints.push({ coord: node, type: 'loc' });
});
} else {
columnHeaders.forEach((nodeName) => {
var node = this.findNodeByName(nodeName);
if (!node) throw new Error(util.format('*** unknown node "%s"', nodeName));
waypoints.push({ coord: node, type: 'dst' });
});
rowHeaders.forEach((nodeName) => {
var node = this.findNodeByName(nodeName);
if (!node) throw new Error(util.format('*** unknown node "%s"', nodeName));
waypoints.push({ coord: node, type: 'src' });
});
}
var actual = [];
actual.push(table.headers);
this.reprocessAndLoadData((e) => {
if (e) return callback(e);
// compute matrix
this.requestTable(waypoints, params, (err, response) => {
if (err) return callback(err);
if (!response.body.length) return callback(new Error('Invalid response body'));
var json = JSON.parse(response.body);
var result = json[annotation].map(row => {
var hashes = {};
row.forEach((v, i) => { hashes[tableRows[0][i+1]] = parse(v) ? '' : v; });
return hashes;
});
} else {
columnHeaders.forEach((nodeName) => {
var node = this.findNodeByName(nodeName);
if (!node) throw new Error(util.format('*** unknown node "%s"', nodeName));
waypoints.push({ coord: node, type: 'dst' });
});
rowHeaders.forEach((nodeName) => {
var node = this.findNodeByName(nodeName);
if (!node) throw new Error(util.format('*** unknown node "%s"', nodeName));
waypoints.push({ coord: node, type: 'src' });
});
}
var actual = [];
actual.push(table.headers);
this.reprocessAndLoadData((e) => {
if (e) return callback(e);
// compute matrix
var params = this.queryParams;
this.requestTable(waypoints, params, (err, response) => {
if (err) return callback(err);
if (!response.body.length) return callback(new Error('Invalid response body'));
var json = JSON.parse(response.body);
var result = json['durations'].map(row => {
var hashes = {};
row.forEach((v, i) => { hashes[tableRows[0][i+1]] = isNaN(parseInt(v)) ? '' : v; });
return hashes;
});
var testRow = (row, ri, cb) => {
for (var k in result[ri]) {
if (this.FuzzyMatch.match(result[ri][k], row[k])) {
result[ri][k] = row[k];
} else if (row[k] === '' && result[ri][k] === NO_ROUTE) {
result[ri][k] = '';
} else {
result[ri][k] = result[ri][k].toString();
}
var testRow = (row, ri, cb) => {
for (var k in result[ri]) {
if (this.FuzzyMatch.match(result[ri][k], row[k])) {
result[ri][k] = row[k];
} else if (row[k] === '' && result[ri][k] === noRoute) {
result[ri][k] = '';
} else {
result[ri][k] = result[ri][k].toString();
}
}
result[ri][''] = row[''];
cb(null, result[ri]);
};
result[ri][''] = row[''];
cb(null, result[ri]);
};
this.processRowsAndDiff(table, testRow, callback);
});
this.processRowsAndDiff(table, testRow, callback);
});
});
};
}

View File

@ -51,7 +51,7 @@ module.exports = function () {
.defer(rimraf, this.scenarioLogFile)
.awaitAll(callback);
// uncomment to get path to logfile
// console.log(" Writing logging output to " + this.scenarioLogFile)
console.log(' Writing logging output to ' + this.scenarioLogFile);
});
this.After((scenario, callback) => {

View File

@ -1,14 +1,12 @@
@matrix @testbot
@matrix @testbot @ch
Feature: Basic Distance Matrix
# note that results are travel time, specified in 1/10th of seconds
# since testbot uses a default speed of 100m/10s, the result matches
# the number of meters as long as the way type is the default 'primary'
# note that results of travel distance are in metres
Background:
Given the profile "testbot"
And the partition extra arguments "--small-component-size 1 --max-cell-sizes 2,4,8,16"
Scenario: Testbot - Travel time matrix of minimal network
Scenario: Testbot - Travel distance matrix of minimal network
Given the node map
"""
a b
@ -18,12 +16,101 @@ Feature: Basic Distance Matrix
| nodes |
| ab |
When I request a travel time matrix I should get
| | a | b |
| a | 0 | 10 |
| b | 10 | 0 |
When I request a travel distance matrix I should get
| | a | b |
| a | 0 | 100 |
| b | 100 | 0 |
Scenario: Testbot - Travel time matrix with different way speeds
Scenario: Testbot - Travel distance matrix of minimal network with toll exclude
Given the query options
| exclude | toll |
Given the node map
"""
a b
c d
"""
And the ways
| nodes | highway | toll | # |
| ab | motorway | | not drivable for exclude=motorway |
| cd | primary | | always drivable |
| ac | primary | yes | not drivable for exclude=toll and exclude=motorway,toll |
| bd | motorway | yes | not drivable for exclude=toll and exclude=motorway,toll |
When I request a travel distance matrix I should get
| | a | b | c | d |
| a | 0 | 100 | | |
| b | 100 | 0 | | |
| c | | | 0 | 100 |
| d | | | 100 | 0 |
Scenario: Testbot - Travel distance matrix of minimal network with motorway exclude
Given the query options
| exclude | motorway |
Given the node map
"""
a b
c d
"""
And the ways
| nodes | highway | # |
| ab | motorway | not drivable for exclude=motorway |
| cd | residential | |
| ac | residential | |
| bd | residential | |
When I request a travel distance matrix I should get
| | a | b | c | d |
| a | 0 | 300 | 100 | 200 |
Scenario: Testbot - Travel distance matrix of minimal network disconnected motorway exclude
Given the query options
| exclude | motorway |
And the extract extra arguments "--small-component-size 4"
Given the node map
"""
ab efgh
cd
"""
And the ways
| nodes | highway | # |
| be | motorway | not drivable for exclude=motorway |
| abcd | residential | |
| efgh | residential | |
When I request a travel distance matrix I should get
| | a | b | e |
| a | 0 | 50 | |
Scenario: Testbot - Travel distance matrix of minimal network with motorway and toll excludes
Given the query options
| exclude | motorway,toll |
Given the node map
"""
a b e f
c d g h
"""
And the ways
| nodes | highway | toll | # |
| be | motorway | | not drivable for exclude=motorway |
| dg | primary | yes | not drivable for exclude=toll |
| abcd | residential | | |
| efgh | residential | | |
When I request a travel distance matrix I should get
| | a | b | e | g |
| a | 0 | 100 | | |
Scenario: Testbot - Travel distance matrix with different way speeds
Given the node map
"""
a b c d
@ -35,40 +122,25 @@ Feature: Basic Distance Matrix
| bc | secondary |
| cd | tertiary |
When I request a travel time matrix I should get
| | a | b | c | d |
| a | 0 | 10 | 30 | 60 |
| b | 10 | 0 | 20 | 50 |
| c | 30 | 20 | 0 | 30 |
| d | 60 | 50 | 30 | 0 |
When I request a travel distance matrix I should get
| | a | b | c | d |
| a | 0 | 100 | 200 | 299.9 |
| b | 100 | 0 | 100 | 200 |
| c | 200 | 100 | 0 | 100 |
| d | 299.9 | 200 | 100 | 0 |
When I request a travel time matrix I should get
| | a | b | c | d |
| a | 0 | 10 | 30 | 60 |
When I request a travel distance matrix I should get
| | a | b | c | d |
| a | 0 | 100 | 200 | 299.9 |
When I request a travel time matrix I should get
| | a |
| a | 0 |
| b | 10 |
| c | 30 |
| d | 60 |
When I request a travel distance matrix I should get
| | a |
| a | 0 |
| b | 100 |
| c | 200 |
| d | 299.9 |
Scenario: Testbot - Travel time matrix with fuzzy match
Given the node map
"""
a b
"""
And the ways
| nodes |
| ab |
When I request a travel time matrix I should get
| | a | b |
| a | 0 | 10 |
| b | 10 | 0 |
Scenario: Testbot - Travel time matrix of small grid
Scenario: Testbot - Travel distance matrix of small grid
Given the node map
"""
a b c
@ -83,14 +155,14 @@ Feature: Basic Distance Matrix
| be |
| cf |
When I request a travel time matrix I should get
| | a | b | e | f |
| a | 0 | 10 | 20 | 30 |
| b | 10 | 0 | 10 | 20 |
| e | 20 | 10 | 0 | 10 |
| f | 30 | 20 | 10 | 0 |
When I request a travel distance matrix I should get
| | a | b | e | f |
| a | 0 | 100 | 200 | 299.9 |
| b | 100 | 0 | 100 | 200 |
| e | 200 | 100 | 0 | 100 |
| f | 299.9 | 200 | 100 | 0 |
Scenario: Testbot - Travel time matrix of network with unroutable parts
Scenario: Testbot - Travel distance matrix of network with unroutable parts
Given the node map
"""
a b
@ -100,12 +172,12 @@ Feature: Basic Distance Matrix
| nodes | oneway |
| ab | yes |
When I request a travel time matrix I should get
| | a | b |
| a | 0 | 10 |
| b | | 0 |
When I request a travel distance matrix I should get
| | a | b |
| a | 0 | 100 |
| b | | 0 |
Scenario: Testbot - Travel time matrix of network with oneways
Scenario: Testbot - Travel distance matrix of network with oneways
Given the node map
"""
x a b y
@ -118,14 +190,14 @@ Feature: Basic Distance Matrix
| xa | |
| by | |
When I request a travel time matrix I should get
| | x | y | d | e |
| x | 0 | 30 | 40 | 30 |
| y | 50 | 0 | 30 | 20 |
| d | 20 | 30 | 0 | 30 |
| e | 30 | 40 | 10 | 0 |
When I request a travel distance matrix I should get
| | x | y | d | e |
| x | 0 | 299.9 | 399.9 | 299.9 |
| y | 499.9 | 0 | 299.9 | 200 |
| d | 200 | 299.9 | 0 | 300 |
| e | 299.9 | 399.9 | 100 | 0 |
Scenario: Testbot - Rectangular travel time matrix
Scenario: Testbot - Rectangular travel distance matrix
Given the node map
"""
a b c
@ -140,51 +212,51 @@ Feature: Basic Distance Matrix
| be |
| cf |
When I request a travel time matrix I should get
| | a | b | e | f |
| a | 0 | 10 | 20 | 30 |
When I request a travel distance matrix I should get
| | a | b | e | f |
| a | 0 | 100 | 200 | 299.9 |
When I request a travel time matrix I should get
| | a |
| a | 0 |
| b | 10 |
| e | 20 |
| f | 30 |
When I request a travel distance matrix I should get
| | a |
| a | 0 |
| b | 100 |
| e | 200 |
| f | 299.9 |
When I request a travel time matrix I should get
| | a | b | e | f |
| a | 0 | 10 | 20 | 30 |
| b | 10 | 0 | 10 | 20 |
When I request a travel distance matrix I should get
| | a | b | e | f |
| a | 0 | 100 | 200 | 299.9 |
| b | 100 | 0 | 100 | 200 |
When I request a travel time matrix I should get
| | a | b |
| a | 0 | 10 |
| b | 10 | 0 |
| e | 20 | 10 |
| f | 30 | 20 |
When I request a travel distance matrix I should get
| | a | b |
| a | 0 | 100 |
| b | 100 | 0 |
| e | 200 | 100 |
| f | 299.9 | 200 |
When I request a travel time matrix I should get
| | a | b | e | f |
| a | 0 | 10 | 20 | 30 |
| b | 10 | 0 | 10 | 20 |
| e | 20 | 10 | 0 | 10 |
When I request a travel distance matrix I should get
| | a | b | e | f |
| a | 0 | 100 | 200 | 299.9 |
| b | 100 | 0 | 100 | 200 |
| e | 200 | 100 | 0 | 100 |
When I request a travel time matrix I should get
| | a | b | e |
| a | 0 | 10 | 20 |
| b | 10 | 0 | 10 |
| e | 20 | 10 | 0 |
| f | 30 | 20 | 10 |
When I request a travel distance matrix I should get
| | a | b | e |
| a | 0 | 100 | 200 |
| b | 100 | 0 | 100 |
| e | 200 | 100 | 0 |
| f | 299.9 | 200 | 100 |
When I request a travel time matrix I should get
| | a | b | e | f |
| a | 0 | 10 | 20 | 30 |
| b | 10 | 0 | 10 | 20 |
| e | 20 | 10 | 0 | 10 |
| f | 30 | 20 | 10 | 0 |
When I request a travel distance matrix I should get
| | a | b | e | f |
| a | 0 | 100 | 200 | 299.9 |
| b | 100 | 0 | 100 | 200 |
| e | 200 | 100 | 0 | 100 |
| f | 299.9 | 200 | 100 | 0 |
Scenario: Testbot - Travel time 3x2 matrix
Scenario: Testbot - Travel distance 3x2 matrix
Given the node map
"""
a b c
@ -199,10 +271,11 @@ Feature: Basic Distance Matrix
| be |
| cf |
When I request a travel time matrix I should get
| | b | e | f |
| a | 10 | 20 | 30 |
| b | 0 | 10 | 20 |
When I request a travel distance matrix I should get
| | b | e | f |
| a | 100 | 200 | 299.9 |
| b | 0 | 100 | 200 |
Scenario: Testbot - All coordinates are from same small component
Given a grid size of 300 meters
@ -221,10 +294,10 @@ Feature: Basic Distance Matrix
| da |
| fg |
When I request a travel time matrix I should get
| | f | g |
| f | 0 | 30 |
| g | 30 | 0 |
When I request a travel distance matrix I should get
| | f | g |
| f | 0 | 300 |
| g | 300 | 0 |
Scenario: Testbot - Coordinates are from different small component and snap to big CC
Given a grid size of 300 meters
@ -244,14 +317,25 @@ Feature: Basic Distance Matrix
| fg |
| hi |
When I request a travel time matrix I should get
| | f | g | h | i |
| f | 0 | 30 | 0 | 30 |
| g | 30 | 0 | 30 | 0 |
| h | 0 | 30 | 0 | 30 |
| i | 30 | 0 | 30 | 0 |
When I route I should get
| from | to | distance |
| f | g | 300m |
| f | i | 300m |
| g | f | 300m |
| g | h | 300m |
| h | g | 300m |
| h | i | 300m |
| i | f | 300m |
| i | h | 300m |
Scenario: Testbot - Travel time matrix with loops
When I request a travel distance matrix I should get
| | f | g | h | i |
| f | 0 | 300 | 0 | 300 |
| g | 300 | 0 | 300 | 0 |
| h | 0 | 300 | 0 | 300 |
| i | 300 | 0 | 300 | 0 |
Scenario: Testbot - Travel distance matrix with loops
Given the node map
"""
a 1 2 b
@ -265,14 +349,15 @@ Feature: Basic Distance Matrix
| cd | yes |
| da | yes |
When I request a travel time matrix I should get
| | 1 | 2 | 3 | 4 |
| 1 | 0 | 10 +-1 | 40 +-1 | 50 +-1 |
| 2 | 70 +-1 | 0 | 30 +-1 | 40 +-1 |
| 3 | 40 +-1 | 50 +-1 | 0 | 10 +-1 |
| 4 | 30 +-1 | 40 +-1 | 70 +-1 | 0 |
When I request a travel distance matrix I should get
| | 1 | 2 | 3 | 4 |
| 1 | 0 | 100 | 399.9 | 499.9 |
| 2 | 699.9 | 0 | 299.9 | 399.9 |
| 3 | 399.9 | 499.9 | 0 | 100 |
| 4 | 299.9 | 399.9 | 699.9 | 0 |
Scenario: Testbot - Travel time matrix based on segment durations
Scenario: Testbot - Travel distance matrix based on segment durations
Given the profile file
"""
local functions = require('testbot')
@ -301,20 +386,19 @@ Feature: Basic Distance Matrix
"""
And the ways
| nodes |
| abcd |
| ce |
| nodes |
| abcd |
| ce |
When I request a travel time matrix I should get
| | a | b | c | d | e |
| a | 0 | 11 | 22 | 33 | 33 |
| b | 11 | 0 | 11 | 22 | 22 |
| c | 22 | 11 | 0 | 11 | 11 |
| d | 33 | 22 | 11 | 0 | 22 |
| e | 33 | 22 | 11 | 22 | 0 |
When I request a travel distance matrix I should get
| | a | b | c | d | e |
| a | 0 | 100 | 200 | 299.9 | 399.9 |
| b | 100 | 0 | 100 | 200 | 300 |
| c | 200 | 100 | 0 | 100 | 200 |
| d | 299.9 | 200 | 100 | 0 | 300 |
| e | 399.9 | 300 | 200 | 300 | 0 |
Scenario: Testbot - Travel time matrix for alternative loop paths
Scenario: Testbot - Travel distance matrix for alternative loop paths
Given the profile file
"""
local functions = require('testbot')
@ -350,37 +434,19 @@ Feature: Basic Distance Matrix
| dc | yes |
| ca | yes |
When I request a travel time matrix I should get
| | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 1 | 0 | 11 | 3 | 2 | 6 | 5 | 8.9 | 7.9 |
| 2 | 1 | 0 | 4 | 3 | 7 | 6 | 9.9 | 8.9 |
| 3 | 9 | 8 | 0 | 11 | 3 | 2 | 5.9 | 4.9 |
| 4 | 10 | 9 | 1 | 0 | 4 | 3 | 6.9 | 5.9 |
| 5 | 6 | 5 | 9 | 8 | 0 | 11 | 2.9 | 1.9 |
| 6 | 7 | 6 | 10 | 9 | 1 | 0 | 3.9 | 2.9 |
| 7 | 3.1 | 2.1 | 6.1 | 5.1 | 9.1 | 8.1 | 0 | 11 |
| 8 | 4.1 | 3.1 | 7.1 | 6.1 | 10.1 | 9.1 | 1 | 0 |
When I request a travel distance matrix I should get
| | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 1 | 0 | 1099.8 | 300 | 200 | 599.9 | 499.9 | 899.9 | 799.9 |
| 2 | 100 | 0 | 399.9 | 299.9 | 699.9 | 599.9 | 999.8 | 899.9 |
| 3 | 899.9 | 799.9 | 0 | 1099.8 | 299.9 | 200 | 599.9 | 499.9 |
| 4 | 999.8 | 899.9 | 100 | 0 | 399.9 | 300 | 699.9 | 599.9 |
| 5 | 599.9 | 499.9 | 899.9 | 799.9 | 0 | 1099.8 | 300 | 200 |
| 6 | 699.9 | 599.9 | 999.8 | 899.9 | 100 | 0 | 399.9 | 299.9 |
| 7 | 299.9 | 200 | 599.9 | 499.9 | 899.9 | 799.9 | 0 | 1099.8 |
| 8 | 399.9 | 300 | 699.9 | 599.9 | 999.8 | 899.9 | 100 | 0 |
Scenario: Testbot - Travel time matrix with ties
Given the profile file
"""
local functions = require('testbot')
functions.process_segment = function(profile, segment)
segment.weight = 1
segment.duration = 1
end
functions.process_turn = function(profile, turn)
if turn.angle >= 0 then
turn.duration = 16
else
turn.duration = 4
end
turn.weight = 0
end
return functions
"""
And the node map
Scenario: Testbot - Travel distance matrix with ties
Given the node map
"""
a b
@ -388,24 +454,29 @@ Feature: Basic Distance Matrix
"""
And the ways
| nodes |
| ab |
| ac |
| bd |
| dc |
| nodes |
| ab |
| ac |
| bd |
| dc |
When I route I should get
| from | to | route | distance | time | weight |
| a | c | ac,ac | 200m | 5s | 5 |
| from | to | route | distance | time | weight |
| a | c | ac,ac | 200m | 20s | 20 |
When I request a travel time matrix I should get
| | a | b | c | d |
| a | 0 | 1 | 5 | 10 |
When I route I should get
| from | to | route | distance |
| a | b | ab,ab | 299.9m |
| a | c | ac,ac | 200m |
| a | d | ab,bd,bd | 499.9m |
When I request a travel time matrix I should get
| | a |
| a | 0 |
| b | 1 |
| c | 15 |
| d | 10 |
When I request a travel distance matrix I should get
| | a | b | c | d |
| a | 0 | 299.9 | 200 | 499.9 |
When I request a travel distance matrix I should get
| | a |
| a | 0 |
| b | 299.9 |
| c | 200 |
| d | 499.9 |

View File

@ -0,0 +1,490 @@
@matrix @testbot
Feature: Basic Duration Matrix
# note that results of travel time are in seconds
Background:
Given the profile "testbot"
And the partition extra arguments "--small-component-size 1 --max-cell-sizes 2,4,8,16"
Scenario: Testbot - Travel time matrix of minimal network
Given the node map
"""
a b
"""
And the ways
| nodes |
| ab |
When I request a travel time matrix I should get
| | a | b |
| a | 0 | 10 |
| b | 10 | 0 |
@ch
Scenario: Testbot - Travel time matrix of minimal network with toll exclude
Given the query options
| exclude | toll |
Given the node map
"""
a b
c d
"""
And the ways
| nodes | highway | toll | # |
| ab | motorway | | not drivable for exclude=motorway |
| cd | primary | | always drivable |
| ac | motorway | yes | not drivable for exclude=toll and exclude=motorway,toll |
| bd | motorway | yes | not drivable for exclude=toll and exclude=motorway,toll |
When I request a travel time matrix I should get
| | a | b | c | d |
| a | 0 | 15 | | |
| b | 15 | 0 | | |
| c | | | 0 | 10 |
| d | | | 10 | 0 |
@ch
Scenario: Testbot - Travel time matrix of minimal network with motorway exclude
Given the query options
| exclude | motorway |
Given the node map
"""
a b
c d
"""
And the ways
| nodes | highway | # |
| ab | motorway | not drivable for exclude=motorway |
| cd | residential | |
| ac | residential | |
| bd | residential | |
When I request a travel time matrix I should get
| | a | b | c | d |
| a | 0 | 45 | 15 | 30 |
@ch
Scenario: Testbot - Travel time matrix of minimal network disconnected motorway exclude
Given the query options
| exclude | motorway |
Given the node map
"""
ab efgh
cd
"""
And the ways
| nodes | highway | # |
| be | motorway | not drivable for exclude=motorway |
| abcd | residential | |
| efgh | residential | |
When I request a travel time matrix I should get
| | a | b | e |
| a | 0 | 7.5 | |
@ch
Scenario: Testbot - Travel time matrix of minimal network with motorway and toll excludes
Given the query options
| exclude | motorway,toll |
Given the node map
"""
a b e f
c d g h
"""
And the ways
| nodes | highway | toll | # |
| be | motorway | | not drivable for exclude=motorway |
| dg | primary | yes | not drivable for exclude=toll |
| abcd | residential | | |
| efgh | residential | | |
When I request a travel time matrix I should get
| | a | b | e | g |
| a | 0 | 15 | | |
Scenario: Testbot - Travel time matrix with different way speeds
Given the node map
"""
a b c d
"""
And the ways
| nodes | highway |
| ab | primary |
| bc | secondary |
| cd | tertiary |
When I request a travel time matrix I should get
| | a | b | c | d |
| a | 0 | 10 | 30 | 60 |
| b | 10 | 0 | 20 | 50 |
| c | 30 | 20 | 0 | 30 |
| d | 60 | 50 | 30 | 0 |
When I request a travel time matrix I should get
| | a | b | c | d |
| a | 0 | 10 | 30 | 60 |
When I request a travel time matrix I should get
| | a |
| a | 0 |
| b | 10 |
| c | 30 |
| d | 60 |
Scenario: Testbot - Travel time matrix of small grid
Given the node map
"""
a b c
d e f
"""
And the ways
| nodes |
| abc |
| def |
| ad |
| be |
| cf |
When I request a travel time matrix I should get
| | a | b | e | f |
| a | 0 | 10 | 20 | 30 |
| b | 10 | 0 | 10 | 20 |
| e | 20 | 10 | 0 | 10 |
| f | 30 | 20 | 10 | 0 |
Scenario: Testbot - Travel time matrix of network with unroutable parts
Given the node map
"""
a b
"""
And the ways
| nodes | oneway |
| ab | yes |
When I request a travel time matrix I should get
| | a | b |
| a | 0 | 10 |
| b | | 0 |
Scenario: Testbot - Travel time matrix of network with oneways
Given the node map
"""
x a b y
d e
"""
And the ways
| nodes | oneway |
| abeda | yes |
| xa | |
| by | |
When I request a travel time matrix I should get
| | x | y | d | e |
| x | 0 | 30 | 40 | 30 |
| y | 50 | 0 | 30 | 20 |
| d | 20 | 30 | 0 | 30 |
| e | 30 | 40 | 10 | 0 |
Scenario: Testbot - Rectangular travel time matrix
Given the node map
"""
a b c
d e f
"""
And the ways
| nodes |
| abc |
| def |
| ad |
| be |
| cf |
When I request a travel time matrix I should get
| | a | b | e | f |
| a | 0 | 10 | 20 | 30 |
When I request a travel time matrix I should get
| | a |
| a | 0 |
| b | 10 |
| e | 20 |
| f | 30 |
When I request a travel time matrix I should get
| | a | b | e | f |
| a | 0 | 10 | 20 | 30 |
| b | 10 | 0 | 10 | 20 |
When I request a travel time matrix I should get
| | a | b |
| a | 0 | 10 |
| b | 10 | 0 |
| e | 20 | 10 |
| f | 30 | 20 |
When I request a travel time matrix I should get
| | a | b | e | f |
| a | 0 | 10 | 20 | 30 |
| b | 10 | 0 | 10 | 20 |
| e | 20 | 10 | 0 | 10 |
When I request a travel time matrix I should get
| | a | b | e |
| a | 0 | 10 | 20 |
| b | 10 | 0 | 10 |
| e | 20 | 10 | 0 |
| f | 30 | 20 | 10 |
When I request a travel time matrix I should get
| | a | b | e | f |
| a | 0 | 10 | 20 | 30 |
| b | 10 | 0 | 10 | 20 |
| e | 20 | 10 | 0 | 10 |
| f | 30 | 20 | 10 | 0 |
Scenario: Testbot - Travel time 3x2 matrix
Given the node map
"""
a b c
d e f
"""
And the ways
| nodes |
| abc |
| def |
| ad |
| be |
| cf |
When I request a travel time matrix I should get
| | b | e | f |
| a | 10 | 20 | 30 |
| b | 0 | 10 | 20 |
Scenario: Testbot - All coordinates are from same small component
Given a grid size of 300 meters
Given the extract extra arguments "--small-component-size 4"
Given the node map
"""
a b f
d e g
"""
And the ways
| nodes |
| ab |
| be |
| ed |
| da |
| fg |
When I request a travel time matrix I should get
| | f | g |
| f | 0 | 30 |
| g | 30 | 0 |
Scenario: Testbot - Coordinates are from different small component and snap to big CC
Given a grid size of 300 meters
Given the extract extra arguments "--small-component-size 4"
Given the node map
"""
a b f h
d e g i
"""
And the ways
| nodes |
| ab |
| be |
| ed |
| da |
| fg |
| hi |
When I request a travel time matrix I should get
| | f | g | h | i |
| f | 0 | 30 | 0 | 30 |
| g | 30 | 0 | 30 | 0 |
| h | 0 | 30 | 0 | 30 |
| i | 30 | 0 | 30 | 0 |
Scenario: Testbot - Travel time matrix with loops
Given the node map
"""
a 1 2 b
d 4 3 c
"""
And the ways
| nodes | oneway |
| ab | yes |
| bc | yes |
| cd | yes |
| da | yes |
When I request a travel time matrix I should get
| | 1 | 2 | 3 | 4 |
| 1 | 0 | 10 +-1 | 40 +-1 | 50 +-1 |
| 2 | 70 +-1 | 0 | 30 +-1 | 40 +-1 |
| 3 | 40 +-1 | 50 +-1 | 0 | 10 +-1 |
| 4 | 30 +-1 | 40 +-1 | 70 +-1 | 0 |
Scenario: Testbot - Travel time matrix based on segment durations
Given the profile file
"""
local functions = require('testbot')
functions.setup_testbot = functions.setup
functions.setup = function()
local profile = functions.setup_testbot()
profile.traffic_signal_penalty = 0
profile.u_turn_penalty = 0
return profile
end
functions.process_segment = function(profile, segment)
segment.weight = 2
segment.duration = 11
end
return functions
"""
And the node map
"""
a-b-c-d
.
e
"""
And the ways
| nodes |
| abcd |
| ce |
When I request a travel time matrix I should get
| | a | b | c | d | e |
| a | 0 | 11 | 22 | 33 | 33 |
| b | 11 | 0 | 11 | 22 | 22 |
| c | 22 | 11 | 0 | 11 | 11 |
| d | 33 | 22 | 11 | 0 | 22 |
| e | 33 | 22 | 11 | 22 | 0 |
Scenario: Testbot - Travel time matrix for alternative loop paths
Given the profile file
"""
local functions = require('testbot')
functions.setup_testbot = functions.setup
functions.setup = function()
local profile = functions.setup_testbot()
profile.traffic_signal_penalty = 0
profile.u_turn_penalty = 0
profile.weight_precision = 3
return profile
end
functions.process_segment = function(profile, segment)
segment.weight = 777
segment.duration = 3
end
return functions
"""
And the node map
"""
a 2 1 b
7 4
8 3
c 5 6 d
"""
And the ways
| nodes | oneway |
| ab | yes |
| bd | yes |
| dc | yes |
| ca | yes |
When I request a travel time matrix I should get
| | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 1 | 0 | 11 | 3 | 2 | 6 | 5 | 8.9 | 7.9 |
| 2 | 1 | 0 | 4 | 3 | 7 | 6 | 9.9 | 8.9 |
| 3 | 9 | 8 | 0 | 11 | 3 | 2 | 5.9 | 4.9 |
| 4 | 10 | 9 | 1 | 0 | 4 | 3 | 6.9 | 5.9 |
| 5 | 6 | 5 | 9 | 8 | 0 | 11 | 2.9 | 1.9 |
| 6 | 7 | 6 | 10 | 9 | 1 | 0 | 3.9 | 2.9 |
| 7 | 3.1 | 2.1 | 6.1 | 5.1 | 9.1 | 8.1 | 0 | 11 |
| 8 | 4.1 | 3.1 | 7.1 | 6.1 | 10.1 | 9.1 | 1 | 0 |
Scenario: Testbot - Travel time matrix with ties
Given the profile file
"""
local functions = require('testbot')
functions.process_segment = function(profile, segment)
segment.weight = 1
segment.duration = 1
end
functions.process_turn = function(profile, turn)
if turn.angle >= 0 then
turn.duration = 16
else
turn.duration = 4
end
turn.weight = 0
end
return functions
"""
And the node map
"""
a b
c d
"""
And the ways
| nodes |
| ab |
| ac |
| bd |
| dc |
When I route I should get
| from | to | route | distance | time | weight |
| a | c | ac,ac | 200m | 5s | 5 |
When I request a travel time matrix I should get
| | a | b | c | d |
| a | 0 | 1 | 5 | 10 |
When I request a travel time matrix I should get
| | a |
| a | 0 |
| b | 1 |
| c | 15 |
| d | 10 |

View File

@ -36,9 +36,10 @@ class TableAPI final : public BaseAPI
{
}
virtual void MakeResponse(const std::vector<EdgeWeight> &durations,
const std::vector<PhantomNode> &phantoms,
util::json::Object &response) const
virtual void
MakeResponse(const std::pair<std::vector<EdgeDuration>, std::vector<EdgeDistance>> &tables,
const std::vector<PhantomNode> &phantoms,
util::json::Object &response) const
{
auto number_of_sources = parameters.sources.size();
auto number_of_destinations = parameters.destinations.size();
@ -64,8 +65,18 @@ class TableAPI final : public BaseAPI
response.values["destinations"] = MakeWaypoints(phantoms, parameters.destinations);
}
response.values["durations"] =
MakeTable(durations, number_of_sources, number_of_destinations);
if (parameters.annotations & TableParameters::AnnotationsType::Duration)
{
response.values["durations"] =
MakeDurationTable(tables.first, number_of_sources, number_of_destinations);
}
if (parameters.annotations & TableParameters::AnnotationsType::Distance)
{
response.values["distances"] =
MakeDistanceTable(tables.second, number_of_sources, number_of_destinations);
}
response.values["code"] = "Ok";
}
@ -97,9 +108,9 @@ class TableAPI final : public BaseAPI
return json_waypoints;
}
virtual util::json::Array MakeTable(const std::vector<EdgeWeight> &values,
std::size_t number_of_rows,
std::size_t number_of_columns) const
virtual util::json::Array MakeDurationTable(const std::vector<EdgeWeight> &values,
std::size_t number_of_rows,
std::size_t number_of_columns) const
{
util::json::Array json_table;
for (const auto row : util::irange<std::size_t>(0UL, number_of_rows))
@ -116,6 +127,7 @@ class TableAPI final : public BaseAPI
{
return util::json::Value(util::json::Null());
}
// division by 10 because the duration is in deciseconds (10s)
return util::json::Value(util::json::Number(duration / 10.));
});
json_table.values.push_back(std::move(json_row));
@ -123,6 +135,34 @@ class TableAPI final : public BaseAPI
return json_table;
}
virtual util::json::Array MakeDistanceTable(const std::vector<EdgeDistance> &values,
std::size_t number_of_rows,
std::size_t number_of_columns) const
{
util::json::Array json_table;
for (const auto row : util::irange<std::size_t>(0UL, number_of_rows))
{
util::json::Array json_row;
auto row_begin_iterator = values.begin() + (row * number_of_columns);
auto row_end_iterator = values.begin() + ((row + 1) * number_of_columns);
json_row.values.resize(number_of_columns);
std::transform(row_begin_iterator,
row_end_iterator,
json_row.values.begin(),
[](const EdgeDistance distance) {
if (distance == INVALID_EDGE_DISTANCE)
{
return util::json::Value(util::json::Null());
}
// round to single decimal place
return util::json::Value(
util::json::Number(std::round(distance * 10) / 10.));
});
json_table.values.push_back(std::move(json_row));
}
return json_table;
}
const TableParameters &parameters;
};

View File

@ -60,6 +60,16 @@ struct TableParameters : public BaseParameters
std::vector<std::size_t> sources;
std::vector<std::size_t> destinations;
enum class AnnotationsType
{
None = 0,
Duration = 0x01,
Distance = 0x02,
All = Duration | Distance
};
AnnotationsType annotations = AnnotationsType::Duration;
TableParameters() = default;
template <typename... Args>
TableParameters(std::vector<std::size_t> sources_,
@ -70,6 +80,16 @@ struct TableParameters : public BaseParameters
{
}
template <typename... Args>
TableParameters(std::vector<std::size_t> sources_,
std::vector<std::size_t> destinations_,
const AnnotationsType annotations_,
Args... args_)
: BaseParameters{std::forward<Args>(args_)...}, sources{std::move(sources_)},
destinations{std::move(destinations_)}, annotations{annotations_}
{
}
bool IsValid() const
{
if (!BaseParameters::IsValid())
@ -79,7 +99,7 @@ struct TableParameters : public BaseParameters
if (coordinates.size() < 2)
return false;
// 1/ The user is able to specify duplicates in srcs and dsts, in that case it's her fault
// 1/ The user is able to specify duplicates in srcs and dsts, in that case it's their fault
// 2/ len(srcs) and len(dsts) smaller or equal to len(locations)
if (sources.size() > coordinates.size())
@ -100,6 +120,26 @@ struct TableParameters : public BaseParameters
return true;
}
};
inline bool operator&(TableParameters::AnnotationsType lhs, TableParameters::AnnotationsType rhs)
{
return static_cast<bool>(
static_cast<std::underlying_type_t<TableParameters::AnnotationsType>>(lhs) &
static_cast<std::underlying_type_t<TableParameters::AnnotationsType>>(rhs));
}
inline TableParameters::AnnotationsType operator|(TableParameters::AnnotationsType lhs,
TableParameters::AnnotationsType rhs)
{
return (TableParameters::AnnotationsType)(
static_cast<std::underlying_type_t<TableParameters::AnnotationsType>>(lhs) |
static_cast<std::underlying_type_t<TableParameters::AnnotationsType>>(rhs));
}
inline TableParameters::AnnotationsType operator|=(TableParameters::AnnotationsType lhs,
TableParameters::AnnotationsType rhs)
{
return lhs = lhs | rhs;
}
}
}
}

View File

@ -133,7 +133,6 @@ class ContiguousInternalMemoryDataFacadeBase : public BaseDataFacade
using RTreeNode = SharedRTree::TreeNode;
extractor::ClassData exclude_mask;
std::string m_timestamp;
extractor::ProfileProperties *m_profile_properties;
extractor::Datasources *m_datasources;
@ -622,7 +621,6 @@ class ContiguousInternalMemoryDataFacade<CH>
const std::size_t exclude_index)
: ContiguousInternalMemoryDataFacadeBase(allocator, metric_name, exclude_index),
ContiguousInternalMemoryAlgorithmDataFacade<CH>(allocator, metric_name, exclude_index)
{
}
};
@ -720,7 +718,6 @@ class ContiguousInternalMemoryDataFacade<MLD> final
const std::size_t exclude_index)
: ContiguousInternalMemoryDataFacadeBase(allocator, metric_name, exclude_index),
ContiguousInternalMemoryAlgorithmDataFacade<MLD>(allocator, metric_name, exclude_index)
{
}
};

View File

@ -33,7 +33,6 @@
#include <boost/range/adaptor/reversed.hpp>
#include <boost/range/any_range.hpp>
#include <cstddef>
#include <string>

View File

@ -13,6 +13,7 @@
#include <algorithm>
#include <cmath>
#include <iterator>
#include <memory>
#include <vector>
@ -447,6 +448,8 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
const auto forward_durations = datafacade.GetUncompressedForwardDurations(geometry_id);
const auto reverse_durations = datafacade.GetUncompressedReverseDurations(geometry_id);
const auto forward_geometry = datafacade.GetUncompressedForwardGeometry(geometry_id);
const auto forward_weight_offset =
std::accumulate(forward_weights.begin(),
forward_weights.begin() + data.fwd_segment_position,
@ -457,12 +460,25 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
forward_durations.begin() + data.fwd_segment_position,
EdgeDuration{0});
EdgeWeight forward_weight = forward_weights[data.fwd_segment_position];
EdgeDuration forward_duration = forward_durations[data.fwd_segment_position];
EdgeDistance forward_distance_offset = 0;
for (auto current = forward_geometry.begin();
current < forward_geometry.begin() + data.fwd_segment_position;
++current)
{
forward_distance_offset += util::coordinate_calculation::haversineDistance(
datafacade.GetCoordinateOfNode(*current),
datafacade.GetCoordinateOfNode(*std::next(current)));
}
BOOST_ASSERT(data.fwd_segment_position <
std::distance(forward_durations.begin(), forward_durations.end()));
EdgeWeight forward_weight = forward_weights[data.fwd_segment_position];
EdgeDuration forward_duration = forward_durations[data.fwd_segment_position];
EdgeDistance forward_distance = util::coordinate_calculation::haversineDistance(
datafacade.GetCoordinateOfNode(forward_geometry(data.fwd_segment_position)),
point_on_segment);
const auto reverse_weight_offset =
std::accumulate(reverse_weights.begin(),
reverse_weights.end() - data.fwd_segment_position - 1,
@ -473,10 +489,23 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
reverse_durations.end() - data.fwd_segment_position - 1,
EdgeDuration{0});
EdgeDistance reverse_distance_offset = 0;
for (auto current = forward_geometry.begin();
current < forward_geometry.end() - data.fwd_segment_position - 2;
++current)
{
reverse_distance_offset += util::coordinate_calculation::haversineDistance(
datafacade.GetCoordinateOfNode(*current),
datafacade.GetCoordinateOfNode(*std::next(current)));
}
EdgeWeight reverse_weight =
reverse_weights[reverse_weights.size() - data.fwd_segment_position - 1];
EdgeDuration reverse_duration =
reverse_durations[reverse_durations.size() - data.fwd_segment_position - 1];
EdgeDistance reverse_distance = util::coordinate_calculation::haversineDistance(
point_on_segment,
datafacade.GetCoordinateOfNode(forward_geometry(data.fwd_segment_position + 1)));
ratio = std::min(1.0, std::max(0.0, ratio));
if (data.forward_segment_id.id != SPECIAL_SEGMENTID)
@ -510,6 +539,10 @@ template <typename RTreeT, typename DataFacadeT> class GeospatialQuery
reverse_weight,
forward_weight_offset,
reverse_weight_offset,
forward_distance,
reverse_distance,
forward_distance_offset,
reverse_distance_offset,
forward_duration,
reverse_duration,
forward_duration_offset,

View File

@ -63,8 +63,8 @@ struct Hint
friend std::ostream &operator<<(std::ostream &, const Hint &);
};
static_assert(sizeof(Hint) == 64 + 4, "Hint is bigger than expected");
constexpr std::size_t ENCODED_HINT_SIZE = 92;
static_assert(sizeof(Hint) == 80 + 4, "Hint is bigger than expected");
constexpr std::size_t ENCODED_HINT_SIZE = 112;
static_assert(ENCODED_HINT_SIZE / 4 * 3 >= sizeof(Hint),
"ENCODED_HINT_SIZE does not match size of Hint");
}

View File

@ -47,10 +47,13 @@ struct PhantomNode
: forward_segment_id{SPECIAL_SEGMENTID, false},
reverse_segment_id{SPECIAL_SEGMENTID, false}, forward_weight(INVALID_EDGE_WEIGHT),
reverse_weight(INVALID_EDGE_WEIGHT), forward_weight_offset(0), reverse_weight_offset(0),
forward_distance(INVALID_EDGE_DISTANCE), reverse_distance(INVALID_EDGE_DISTANCE),
forward_distance_offset(0), reverse_distance_offset(0),
forward_duration(MAXIMAL_EDGE_DURATION), reverse_duration(MAXIMAL_EDGE_DURATION),
forward_duration_offset(0), reverse_duration_offset(0), fwd_segment_position(0),
is_valid_forward_source{false}, is_valid_forward_target{false},
is_valid_reverse_source{false}, is_valid_reverse_target{false}, bearing(0)
{
}
@ -78,6 +81,30 @@ struct PhantomNode
return reverse_duration + reverse_duration_offset;
}
// DO THIS FOR DISTANCE
EdgeDistance GetForwardDistance() const
{
// ..... <-- forward_distance
// .... <-- offset
// ......... <-- desired distance
// x <-- this is PhantomNode.location
// 0----1----2----3----4 <-- EdgeBasedGraph Node segments
BOOST_ASSERT(forward_segment_id.enabled);
return forward_distance + forward_distance_offset;
}
EdgeDistance GetReverseDistance() const
{
// .......... <-- reverse_distance
// ... <-- offset
// ............. <-- desired distance
// x <-- this is PhantomNode.location
// 0----1----2----3----4 <-- EdgeBasedGraph Node segments
BOOST_ASSERT(reverse_segment_id.enabled);
return reverse_distance + reverse_distance_offset;
}
bool IsBidirected() const { return forward_segment_id.enabled && reverse_segment_id.enabled; }
bool IsValid(const unsigned number_of_nodes) const
@ -88,6 +115,8 @@ struct PhantomNode
(reverse_weight != INVALID_EDGE_WEIGHT)) &&
((forward_duration != MAXIMAL_EDGE_DURATION) ||
(reverse_duration != MAXIMAL_EDGE_DURATION)) &&
((forward_distance != INVALID_EDGE_DISTANCE) ||
(reverse_distance != INVALID_EDGE_DISTANCE)) &&
(component.id != INVALID_COMPONENTID);
}
@ -130,6 +159,10 @@ struct PhantomNode
EdgeWeight reverse_weight,
EdgeWeight forward_weight_offset,
EdgeWeight reverse_weight_offset,
EdgeDistance forward_distance,
EdgeDistance reverse_distance,
EdgeDistance forward_distance_offset,
EdgeDistance reverse_distance_offset,
EdgeWeight forward_duration,
EdgeWeight reverse_duration,
EdgeWeight forward_duration_offset,
@ -144,7 +177,9 @@ struct PhantomNode
: forward_segment_id{other.forward_segment_id},
reverse_segment_id{other.reverse_segment_id}, forward_weight{forward_weight},
reverse_weight{reverse_weight}, forward_weight_offset{forward_weight_offset},
reverse_weight_offset{reverse_weight_offset}, forward_duration{forward_duration},
reverse_weight_offset{reverse_weight_offset}, forward_distance{forward_distance},
reverse_distance{reverse_distance}, forward_distance_offset{forward_distance_offset},
reverse_distance_offset{reverse_distance_offset}, forward_duration{forward_duration},
reverse_duration{reverse_duration}, forward_duration_offset{forward_duration_offset},
reverse_duration_offset{reverse_duration_offset},
component{component.id, component.is_tiny}, location{location},
@ -162,13 +197,17 @@ struct PhantomNode
EdgeWeight reverse_weight;
EdgeWeight forward_weight_offset; // TODO: try to remove -> requires path unpacking changes
EdgeWeight reverse_weight_offset; // TODO: try to remove -> requires path unpacking changes
EdgeDistance forward_distance;
EdgeDistance reverse_distance;
EdgeDistance forward_distance_offset; // TODO: try to remove -> requires path unpacking changes
EdgeDistance reverse_distance_offset; // TODO: try to remove -> requires path unpacking changes
EdgeWeight forward_duration;
EdgeWeight reverse_duration;
EdgeWeight forward_duration_offset; // TODO: try to remove -> requires path unpacking changes
EdgeWeight reverse_duration_offset; // TODO: try to remove -> requires path unpacking changes
ComponentID component;
util::Coordinate location;
util::Coordinate location; // this is the coordinate of x
util::Coordinate input_location;
unsigned short fwd_segment_position;
// is phantom node valid to be used as source or target
@ -180,7 +219,7 @@ struct PhantomNode
unsigned short bearing : 12;
};
static_assert(sizeof(PhantomNode) == 64, "PhantomNode has more padding then expected");
static_assert(sizeof(PhantomNode) == 80, "PhantomNode has more padding then expected");
using PhantomNodePair = std::pair<PhantomNode, PhantomNode>;

View File

@ -30,10 +30,12 @@ class RoutingAlgorithmsInterface
virtual InternalRouteResult
DirectShortestPathSearch(const PhantomNodes &phantom_node_pair) const = 0;
virtual std::vector<EdgeDuration>
virtual std::pair<std::vector<EdgeDuration>, std::vector<EdgeDistance>>
ManyToManySearch(const std::vector<PhantomNode> &phantom_nodes,
const std::vector<std::size_t> &source_indices,
const std::vector<std::size_t> &target_indices) const = 0;
const std::vector<std::size_t> &target_indices,
const bool calculate_distance,
const bool calculate_duration) const = 0;
virtual routing_algorithms::SubMatchingList
MapMatching(const routing_algorithms::CandidateLists &candidates_list,
@ -81,10 +83,12 @@ template <typename Algorithm> class RoutingAlgorithms final : public RoutingAlgo
InternalRouteResult
DirectShortestPathSearch(const PhantomNodes &phantom_nodes) const final override;
std::vector<EdgeDuration>
virtual std::pair<std::vector<EdgeDuration>, std::vector<EdgeDistance>>
ManyToManySearch(const std::vector<PhantomNode> &phantom_nodes,
const std::vector<std::size_t> &source_indices,
const std::vector<std::size_t> &target_indices) const final override;
const std::vector<std::size_t> &target_indices,
const bool calculate_distance,
const bool calculate_duration) const final override;
routing_algorithms::SubMatchingList
MapMatching(const routing_algorithms::CandidateLists &candidates_list,
@ -184,10 +188,12 @@ inline routing_algorithms::SubMatchingList RoutingAlgorithms<Algorithm>::MapMatc
}
template <typename Algorithm>
std::vector<EdgeDuration> RoutingAlgorithms<Algorithm>::ManyToManySearch(
const std::vector<PhantomNode> &phantom_nodes,
const std::vector<std::size_t> &_source_indices,
const std::vector<std::size_t> &_target_indices) const
std::pair<std::vector<EdgeDuration>, std::vector<EdgeDistance>>
RoutingAlgorithms<Algorithm>::ManyToManySearch(const std::vector<PhantomNode> &phantom_nodes,
const std::vector<std::size_t> &_source_indices,
const std::vector<std::size_t> &_target_indices,
const bool calculate_distance,
const bool calculate_duration) const
{
BOOST_ASSERT(!phantom_nodes.empty());
@ -205,8 +211,13 @@ std::vector<EdgeDuration> RoutingAlgorithms<Algorithm>::ManyToManySearch(
std::iota(target_indices.begin(), target_indices.end(), 0);
}
return routing_algorithms::manyToManySearch(
heaps, *facade, phantom_nodes, std::move(source_indices), std::move(target_indices));
return routing_algorithms::manyToManySearch(heaps,
*facade,
phantom_nodes,
std::move(source_indices),
std::move(target_indices),
calculate_distance,
calculate_duration);
}
template <typename Algorithm>

View File

@ -21,17 +21,26 @@ namespace
struct NodeBucket
{
NodeID middle_node;
NodeID parent_node;
unsigned column_index; // a column in the weight/duration matrix
EdgeWeight weight;
EdgeDuration duration;
NodeBucket(NodeID middle_node, unsigned column_index, EdgeWeight weight, EdgeDuration duration)
: middle_node(middle_node), column_index(column_index), weight(weight), duration(duration)
NodeBucket(NodeID middle_node,
NodeID parent_node,
unsigned column_index,
EdgeWeight weight,
EdgeDuration duration)
: middle_node(middle_node), parent_node(parent_node), column_index(column_index),
weight(weight), duration(duration)
{
}
// partial order comparison
bool operator<(const NodeBucket &rhs) const { return middle_node < rhs.middle_node; }
bool operator<(const NodeBucket &rhs) const
{
return std::tie(middle_node, column_index) < std::tie(rhs.middle_node, rhs.column_index);
}
// functor for equal_range
struct Compare
@ -46,15 +55,36 @@ struct NodeBucket
return lhs < rhs.middle_node;
}
};
// functor for equal_range
struct ColumnCompare
{
unsigned column_idx;
ColumnCompare(unsigned column_idx) : column_idx(column_idx){};
bool operator()(const NodeBucket &lhs, const NodeID &rhs) const // lowerbound
{
return std::tie(lhs.middle_node, lhs.column_index) < std::tie(rhs, column_idx);
}
bool operator()(const NodeID &lhs, const NodeBucket &rhs) const // upperbound
{
return std::tie(lhs, column_idx) < std::tie(rhs.middle_node, rhs.column_index);
}
};
};
}
template <typename Algorithm>
std::vector<EdgeDuration> manyToManySearch(SearchEngineData<Algorithm> &engine_working_data,
const DataFacade<Algorithm> &facade,
const std::vector<PhantomNode> &phantom_nodes,
const std::vector<std::size_t> &source_indices,
const std::vector<std::size_t> &target_indices);
std::pair<std::vector<EdgeDuration>, std::vector<EdgeDistance>>
manyToManySearch(SearchEngineData<Algorithm> &engine_working_data,
const DataFacade<Algorithm> &facade,
const std::vector<PhantomNode> &phantom_nodes,
const std::vector<std::size_t> &source_indices,
const std::vector<std::size_t> &target_indices,
const bool calculate_distance,
const bool calculate_duration);
} // namespace routing_algorithms
} // namespace engine

View File

@ -181,6 +181,7 @@ void annotatePath(const FacadeT &facade,
BOOST_ASSERT(datasource_vector.size() > 0);
BOOST_ASSERT(weight_vector.size() + 1 == id_vector.size());
BOOST_ASSERT(duration_vector.size() + 1 == id_vector.size());
const bool is_first_segment = unpacked_path.empty();
const std::size_t start_index =
@ -405,6 +406,22 @@ InternalRouteResult extractRoute(const DataFacade<AlgorithmT> &facade,
return raw_route_data;
}
template <typename FacadeT> EdgeDistance computeEdgeDistance(const FacadeT &facade, NodeID node_id)
{
const auto geometry_index = facade.GetGeometryIndex(node_id);
EdgeDistance total_distance = 0.0;
auto geometry_range = facade.GetUncompressedForwardGeometry(geometry_index.id);
for (auto current = geometry_range.begin(); current < geometry_range.end() - 1; ++current)
{
total_distance += util::coordinate_calculation::haversineDistance(
facade.GetCoordinateOfNode(*current), facade.GetCoordinateOfNode(*std::next(current)));
}
return total_distance;
}
} // namespace routing_algorithms
} // namespace engine
} // namespace osrm

View File

@ -288,6 +288,106 @@ void unpackPath(const DataFacade<Algorithm> &facade,
}
}
template <typename BidirectionalIterator>
EdgeDistance calculateEBGNodeAnnotations(const DataFacade<Algorithm> &facade,
BidirectionalIterator packed_path_begin,
BidirectionalIterator packed_path_end)
{
// Make sure we have at least something to unpack
if (packed_path_begin == packed_path_end ||
std::distance(packed_path_begin, packed_path_end) <= 1)
return 0;
std::stack<std::tuple<NodeID, NodeID, bool>> recursion_stack;
std::stack<EdgeDistance> distance_stack;
// We have to push the path in reverse order onto the stack because it's LIFO.
for (auto current = std::prev(packed_path_end); current > packed_path_begin;
current = std::prev(current))
{
recursion_stack.emplace(*std::prev(current), *current, false);
}
std::tuple<NodeID, NodeID, bool> edge;
while (!recursion_stack.empty())
{
edge = recursion_stack.top();
recursion_stack.pop();
// Have we processed the edge before? tells us if we have values in the durations stack that
// we can add up
if (!std::get<2>(edge))
{ // haven't processed edge before, so process it in the body!
std::get<2>(edge) = true; // mark that this edge will now be processed
// Look for an edge on the forward CH graph (.forward)
EdgeID smaller_edge_id =
facade.FindSmallestEdge(std::get<0>(edge), std::get<1>(edge), [](const auto &data) {
return data.forward;
});
// If we didn't find one there, the we might be looking at a part of the path that
// was found using the backward search. Here, we flip the node order (.second,
// .first) and only consider edges with the `.backward` flag.
if (SPECIAL_EDGEID == smaller_edge_id)
{
smaller_edge_id =
facade.FindSmallestEdge(std::get<1>(edge),
std::get<0>(edge),
[](const auto &data) { return data.backward; });
}
// If we didn't find anything *still*, then something is broken and someone has
// called this function with bad values.
BOOST_ASSERT_MSG(smaller_edge_id != SPECIAL_EDGEID, "Invalid smaller edge ID");
const auto &data = facade.GetEdgeData(smaller_edge_id);
BOOST_ASSERT_MSG(data.weight != std::numeric_limits<EdgeWeight>::max(),
"edge weight invalid");
// If the edge is a shortcut, we need to add the two halfs to the stack.
if (data.shortcut)
{ // unpack
const NodeID middle_node_id = data.turn_id;
// Note the order here - we're adding these to a stack, so we
// want the first->middle to get visited before middle->second
recursion_stack.emplace(edge);
recursion_stack.emplace(middle_node_id, std::get<1>(edge), false);
recursion_stack.emplace(std::get<0>(edge), middle_node_id, false);
}
else
{
// compute the duration here and put it onto the duration stack using method
// similar to annotatePath but smaller
EdgeDistance distance = computeEdgeDistance(facade, std::get<0>(edge));
distance_stack.emplace(distance);
}
}
else
{ // the edge has already been processed. this means that there are enough values in the
// distances stack
BOOST_ASSERT_MSG(distance_stack.size() >= 2,
"There are not enough (at least 2) values on the distance stack");
EdgeDistance distance1 = distance_stack.top();
distance_stack.pop();
EdgeDistance distance2 = distance_stack.top();
distance_stack.pop();
EdgeDistance distance = distance1 + distance2;
distance_stack.emplace(distance);
}
}
EdgeDistance total_distance = 0;
while (!distance_stack.empty())
{
total_distance += distance_stack.top();
distance_stack.pop();
}
return total_distance;
}
template <typename RandomIter, typename FacadeT>
void unpackPath(const FacadeT &facade,
RandomIter packed_path_begin,
@ -340,6 +440,11 @@ void retrievePackedPathFromSingleHeap(const SearchEngineData<Algorithm>::QueryHe
const NodeID middle_node_id,
std::vector<NodeID> &packed_path);
void retrievePackedPathFromSingleManyToManyHeap(
const SearchEngineData<Algorithm>::ManyToManyQueryHeap &search_heap,
const NodeID middle_node_id,
std::vector<NodeID> &packed_path);
// assumes that heaps are already setup correctly.
// ATTENTION: This only works if no additional offset is supplied next to the Phantom Node
// Offsets.

View File

@ -1064,6 +1064,44 @@ argumentsToTableParameter(const Nan::FunctionCallbackInfo<v8::Value> &args,
}
}
if (obj->Has(Nan::New("annotations").ToLocalChecked()))
{
v8::Local<v8::Value> annotations = obj->Get(Nan::New("annotations").ToLocalChecked());
if (annotations.IsEmpty())
return table_parameters_ptr();
if (!annotations->IsArray())
{
Nan::ThrowError(
"Annotations must an array containing 'duration' or 'distance', or both");
return table_parameters_ptr();
}
v8::Local<v8::Array> annotations_array = v8::Local<v8::Array>::Cast(annotations);
for (std::size_t i = 0; i < annotations_array->Length(); ++i)
{
const Nan::Utf8String annotations_utf8str(annotations_array->Get(i));
std::string annotations_str{*annotations_utf8str,
*annotations_utf8str + annotations_utf8str.length()};
if (annotations_str == "duration")
{
params->annotations =
params->annotations | osrm::TableParameters::AnnotationsType::Duration;
}
else if (annotations_str == "distance")
{
params->annotations =
params->annotations | osrm::TableParameters::AnnotationsType::Distance;
}
else
{
Nan::ThrowError("this 'annotations' param is not supported");
return table_parameters_ptr();
}
}
}
return params;
}

View File

@ -22,11 +22,11 @@ namespace qi = boost::spirit::qi;
template <typename Iterator = std::string::iterator,
typename Signature = void(engine::api::TableParameters &)>
struct TableParametersGrammar final : public BaseParametersGrammar<Iterator, Signature>
struct TableParametersGrammar : public BaseParametersGrammar<Iterator, Signature>
{
using BaseGrammar = BaseParametersGrammar<Iterator, Signature>;
TableParametersGrammar() : BaseGrammar(root_rule)
TableParametersGrammar() : TableParametersGrammar(root_rule)
{
#ifdef BOOST_HAS_LONG_LONG
if (std::is_same<std::size_t, unsigned long long>::value)
@ -51,15 +51,36 @@ struct TableParametersGrammar final : public BaseParametersGrammar<Iterator, Sig
table_rule = destinations_rule(qi::_r1) | sources_rule(qi::_r1);
root_rule = BaseGrammar::query_rule(qi::_r1) > -qi::lit(".json") >
-('?' > (table_rule(qi::_r1) | BaseGrammar::base_rule(qi::_r1)) % '&');
-('?' > (table_rule(qi::_r1) | base_rule(qi::_r1)) % '&');
}
TableParametersGrammar(qi::rule<Iterator, Signature> &root_rule_) : BaseGrammar(root_rule_)
{
using AnnotationsType = engine::api::TableParameters::AnnotationsType;
const auto add_annotation = [](engine::api::TableParameters &table_parameters,
AnnotationsType table_param) {
table_parameters.annotations = table_parameters.annotations | table_param;
};
annotations.add("duration", AnnotationsType::Duration)("distance",
AnnotationsType::Distance);
base_rule = BaseGrammar::base_rule(qi::_r1) |
(qi::lit("annotations=") >
(annotations[ph::bind(add_annotation, qi::_r1, qi::_1)] % ','));
}
protected:
qi::rule<Iterator, Signature> base_rule;
private:
qi::rule<Iterator, Signature> root_rule;
qi::rule<Iterator, Signature> table_rule;
qi::rule<Iterator, Signature> sources_rule;
qi::rule<Iterator, Signature> destinations_rule;
qi::rule<Iterator, std::size_t()> size_t_;
qi::symbols<char, engine::api::TableParameters::AnnotationsType> annotations;
};
}
}

View File

@ -75,6 +75,7 @@ using NameID = std::uint32_t;
using AnnotationID = std::uint32_t;
using EdgeWeight = std::int32_t;
using EdgeDuration = std::int32_t;
using EdgeDistance = float;
using SegmentWeight = std::uint32_t;
using SegmentDuration = std::uint32_t;
using TurnPenalty = std::int16_t; // turn penalty in 100ms units
@ -113,6 +114,7 @@ static const SegmentDuration MAX_SEGMENT_DURATION = INVALID_SEGMENT_DURATION - 1
static const EdgeWeight INVALID_EDGE_WEIGHT = std::numeric_limits<EdgeWeight>::max();
static const EdgeDuration MAXIMAL_EDGE_DURATION = std::numeric_limits<EdgeDuration>::max();
static const TurnPenalty INVALID_TURN_PENALTY = std::numeric_limits<TurnPenalty>::max();
static const EdgeDistance INVALID_EDGE_DISTANCE = std::numeric_limits<EdgeDistance>::max();
// FIXME the bitfields we use require a reduced maximal duration, this should be kept consistent
// within the code base. For now we have to ensure that we don't case 30 bit to -1 and break any

View File

@ -32,7 +32,7 @@ function setup()
primary = 36,
secondary = 18,
tertiary = 12,
steps = 6,
steps = 6
}
}
end

View File

@ -81,16 +81,21 @@ Status TablePlugin::HandleRequest(const RoutingAlgorithmsInterface &algorithms,
}
auto snapped_phantoms = SnapPhantomNodes(phantom_nodes);
auto result_table =
algorithms.ManyToManySearch(snapped_phantoms, params.sources, params.destinations);
if (result_table.empty())
bool request_distance = params.annotations & api::TableParameters::AnnotationsType::Distance;
bool request_duration = params.annotations & api::TableParameters::AnnotationsType::Duration;
auto result_tables_pair = algorithms.ManyToManySearch(
snapped_phantoms, params.sources, params.destinations, request_distance, request_duration);
if ((request_duration & result_tables_pair.first.empty()) ||
(request_distance && result_tables_pair.second.empty()))
{
return Error("NoTable", "No table found", result);
}
api::TableAPI table_api{facade, params};
table_api.MakeResponse(result_table, snapped_phantoms, result);
table_api.MakeResponse(result_tables_pair, snapped_phantoms, result);
return Status::Ok;
}

View File

@ -475,7 +475,6 @@ void encodeVectorTile(const DataFacadeBase &facade,
const auto forward_duration = forward_duration_range[edge.fwd_segment_position];
const auto reverse_duration = reverse_duration_range[reverse_duration_range.size() -
edge.fwd_segment_position - 1];
line_int_index.add(forward_duration);
line_int_index.add(reverse_duration);
}
@ -516,7 +515,6 @@ void encodeVectorTile(const DataFacadeBase &facade,
const auto reverse_duration =
reverse_duration_range[reverse_duration_range.size() -
edge.fwd_segment_position - 1];
const auto forward_datasource_idx =
forward_datasource_range(edge.fwd_segment_position);
const auto reverse_datasource_idx = reverse_datasource_range(

View File

@ -131,7 +131,7 @@ void ManipulateTableForFSE(const std::size_t source_id,
result_table.SetValue(destination_id, i, INVALID_EDGE_WEIGHT);
}
// set destination->source to zero so rountrip treats source and
// set destination->source to zero so roundtrip treats source and
// destination as one location
result_table.SetValue(destination_id, source_id, 0);
@ -216,61 +216,66 @@ Status TripPlugin::HandleRequest(const RoutingAlgorithmsInterface &algorithms,
BOOST_ASSERT(snapped_phantoms.size() == number_of_locations);
// compute the duration table of all phantom nodes
auto result_table = util::DistTableWrapper<EdgeWeight>(
algorithms.ManyToManySearch(snapped_phantoms, {}, {}), number_of_locations);
auto result_duration_table = util::DistTableWrapper<EdgeWeight>(
algorithms
.ManyToManySearch(
snapped_phantoms, {}, {}, /*requestDistance*/ false, /*requestDuration*/ true)
.first,
number_of_locations);
if (result_table.size() == 0)
if (result_duration_table.size() == 0)
{
return Status::Error;
}
const constexpr std::size_t BF_MAX_FEASABLE = 10;
BOOST_ASSERT_MSG(result_table.size() == number_of_locations * number_of_locations,
BOOST_ASSERT_MSG(result_duration_table.size() == number_of_locations * number_of_locations,
"Distance Table has wrong size");
if (!IsStronglyConnectedComponent(result_table))
if (!IsStronglyConnectedComponent(result_duration_table))
{
return Error("NoTrips", "No trip visiting all destinations possible.", json_result);
}
if (fixed_start && fixed_end)
{
ManipulateTableForFSE(source_id, destination_id, result_table);
ManipulateTableForFSE(source_id, destination_id, result_duration_table);
}
std::vector<NodeID> trip;
trip.reserve(number_of_locations);
std::vector<NodeID> duration_trip;
duration_trip.reserve(number_of_locations);
// get an optimized order in which the destinations should be visited
if (number_of_locations < BF_MAX_FEASABLE)
{
trip = trip::BruteForceTrip(number_of_locations, result_table);
duration_trip = trip::BruteForceTrip(number_of_locations, result_duration_table);
}
else
{
trip = trip::FarthestInsertionTrip(number_of_locations, result_table);
duration_trip = trip::FarthestInsertionTrip(number_of_locations, result_duration_table);
}
// rotate result such that roundtrip starts at node with index 0
// thist first if covers scenarios: !fixed_end || fixed_start || (fixed_start && fixed_end)
if (!fixed_end || fixed_start)
{
auto desired_start_index = std::find(std::begin(trip), std::end(trip), 0);
BOOST_ASSERT(desired_start_index != std::end(trip));
std::rotate(std::begin(trip), desired_start_index, std::end(trip));
auto desired_start_index = std::find(std::begin(duration_trip), std::end(duration_trip), 0);
BOOST_ASSERT(desired_start_index != std::end(duration_trip));
std::rotate(std::begin(duration_trip), desired_start_index, std::end(duration_trip));
}
else if (fixed_end && !fixed_start && parameters.roundtrip)
{
auto desired_start_index = std::find(std::begin(trip), std::end(trip), destination_id);
BOOST_ASSERT(desired_start_index != std::end(trip));
std::rotate(std::begin(trip), desired_start_index, std::end(trip));
auto desired_start_index =
std::find(std::begin(duration_trip), std::end(duration_trip), destination_id);
BOOST_ASSERT(desired_start_index != std::end(duration_trip));
std::rotate(std::begin(duration_trip), desired_start_index, std::end(duration_trip));
}
// get the route when visiting all destinations in optimized order
InternalRouteResult route =
ComputeRoute(algorithms, snapped_phantoms, trip, parameters.roundtrip);
ComputeRoute(algorithms, snapped_phantoms, duration_trip, parameters.roundtrip);
// get api response
const std::vector<std::vector<NodeID>> trips = {trip};
const std::vector<std::vector<NodeID>> trips = {duration_trip};
const std::vector<InternalRouteResult> routes = {route};
api::TripAPI trip_api{facade, parameters};
trip_api.MakeResponse(trips, routes, snapped_phantoms, json_result);

View File

@ -1,5 +1,4 @@
#include "engine/routing_algorithms/direct_shortest_path.hpp"
#include "engine/routing_algorithms/routing_base.hpp"
#include "engine/routing_algorithms/routing_base_ch.hpp"
#include "engine/routing_algorithms/routing_base_mld.hpp"

View File

@ -60,8 +60,8 @@ void relaxOutgoingEdges(const DataFacade<Algorithm> &facade,
if (DIRECTION == FORWARD_DIRECTION ? data.forward : data.backward)
{
const NodeID to = facade.GetTarget(edge);
const auto edge_weight = data.weight;
const auto edge_duration = data.duration;
BOOST_ASSERT_MSG(edge_weight > 0, "edge_weight invalid");
@ -85,12 +85,13 @@ void relaxOutgoingEdges(const DataFacade<Algorithm> &facade,
}
void forwardRoutingStep(const DataFacade<Algorithm> &facade,
const unsigned row_idx,
const unsigned number_of_targets,
const std::size_t row_index,
const std::size_t number_of_targets,
typename SearchEngineData<Algorithm>::ManyToManyQueryHeap &query_heap,
const std::vector<NodeBucket> &search_space_with_buckets,
std::vector<EdgeWeight> &weights_table,
std::vector<EdgeDuration> &durations_table,
std::vector<NodeID> &middle_nodes_table,
const PhantomNode &phantom_node)
{
const auto node = query_heap.DeleteMin();
@ -105,12 +106,12 @@ void forwardRoutingStep(const DataFacade<Algorithm> &facade,
for (const auto &current_bucket : boost::make_iterator_range(bucket_list))
{
// Get target id from bucket entry
const auto column_idx = current_bucket.column_index;
const auto column_index = current_bucket.column_index;
const auto target_weight = current_bucket.weight;
const auto target_duration = current_bucket.duration;
auto &current_weight = weights_table[row_idx * number_of_targets + column_idx];
auto &current_duration = durations_table[row_idx * number_of_targets + column_idx];
auto &current_weight = weights_table[row_index * number_of_targets + column_index];
auto &current_duration = durations_table[row_index * number_of_targets + column_index];
// Check if new weight is better
auto new_weight = source_weight + target_weight;
@ -122,12 +123,14 @@ void forwardRoutingStep(const DataFacade<Algorithm> &facade,
{
current_weight = std::min(current_weight, new_weight);
current_duration = std::min(current_duration, new_duration);
middle_nodes_table[row_index * number_of_targets + column_index] = node;
}
}
else if (std::tie(new_weight, new_duration) < std::tie(current_weight, current_duration))
{
current_weight = new_weight;
current_duration = new_duration;
middle_nodes_table[row_index * number_of_targets + column_index] = node;
}
}
@ -136,7 +139,7 @@ void forwardRoutingStep(const DataFacade<Algorithm> &facade,
}
void backwardRoutingStep(const DataFacade<Algorithm> &facade,
const unsigned column_idx,
const unsigned column_index,
typename SearchEngineData<Algorithm>::ManyToManyQueryHeap &query_heap,
std::vector<NodeBucket> &search_space_with_buckets,
const PhantomNode &phantom_node)
@ -144,9 +147,11 @@ void backwardRoutingStep(const DataFacade<Algorithm> &facade,
const auto node = query_heap.DeleteMin();
const auto target_weight = query_heap.GetKey(node);
const auto target_duration = query_heap.GetData(node).duration;
const auto parent = query_heap.GetData(node).parent;
// Store settled nodes in search space bucket
search_space_with_buckets.emplace_back(node, column_idx, target_weight, target_duration);
search_space_with_buckets.emplace_back(
node, parent, column_index, target_weight, target_duration);
relaxOutgoingEdges<REVERSE_DIRECTION>(
facade, node, target_weight, target_duration, query_heap, phantom_node);
@ -154,26 +159,187 @@ void backwardRoutingStep(const DataFacade<Algorithm> &facade,
} // namespace ch
template <>
std::vector<EdgeDuration> manyToManySearch(SearchEngineData<ch::Algorithm> &engine_working_data,
const DataFacade<ch::Algorithm> &facade,
const std::vector<PhantomNode> &phantom_nodes,
const std::vector<std::size_t> &source_indices,
const std::vector<std::size_t> &target_indices)
void retrievePackedPathFromSearchSpace(const NodeID middle_node_id,
const unsigned column_index,
const std::vector<NodeBucket> &search_space_with_buckets,
std::vector<NodeID> &packed_leg)
{
auto bucket_list = std::equal_range(search_space_with_buckets.begin(),
search_space_with_buckets.end(),
middle_node_id,
NodeBucket::ColumnCompare(column_index));
NodeID current_node_id = middle_node_id;
BOOST_ASSERT_MSG(std::distance(bucket_list.first, bucket_list.second) == 1,
"The pointers are not pointing to the same element.");
while (bucket_list.first->parent_node != current_node_id &&
bucket_list.first != search_space_with_buckets.end())
{
current_node_id = bucket_list.first->parent_node;
packed_leg.emplace_back(current_node_id);
bucket_list = std::equal_range(search_space_with_buckets.begin(),
search_space_with_buckets.end(),
current_node_id,
NodeBucket::ColumnCompare(column_index));
}
}
void calculateDistances(typename SearchEngineData<ch::Algorithm>::ManyToManyQueryHeap &query_heap,
const DataFacade<ch::Algorithm> &facade,
const std::vector<PhantomNode> &phantom_nodes,
const std::vector<std::size_t> &target_indices,
const std::size_t row_index,
const std::size_t source_index,
const PhantomNode &source_phantom,
const std::size_t number_of_targets,
const std::vector<NodeBucket> &search_space_with_buckets,
std::vector<EdgeDistance> &distances_table,
const std::vector<NodeID> &middle_nodes_table)
{
std::vector<NodeID> packed_leg;
for (auto column_index : util::irange<std::size_t>(0, number_of_targets))
{
const auto target_index = target_indices[column_index];
const auto &target_phantom = phantom_nodes[target_index];
if (source_index == target_index)
{
distances_table[row_index * number_of_targets + column_index] = 0.0;
continue;
}
NodeID middle_node_id = middle_nodes_table[row_index * number_of_targets + column_index];
if (middle_node_id == SPECIAL_NODEID) // takes care of one-ways
{
distances_table[row_index * number_of_targets + column_index] = INVALID_EDGE_DISTANCE;
continue;
}
// Step 1: Find path from source to middle node
ch::retrievePackedPathFromSingleManyToManyHeap(query_heap, middle_node_id, packed_leg);
std::reverse(packed_leg.begin(), packed_leg.end());
packed_leg.push_back(middle_node_id);
// Step 2: Find path from middle to target node
retrievePackedPathFromSearchSpace(
middle_node_id, column_index, search_space_with_buckets, packed_leg);
if (packed_leg.size() == 1 && (needsLoopForward(source_phantom, target_phantom) ||
needsLoopBackwards(source_phantom, target_phantom)))
{
auto weight = ch::getLoopWeight<false>(facade, packed_leg.front());
if (weight != INVALID_EDGE_WEIGHT)
packed_leg.push_back(packed_leg.front());
}
if (!packed_leg.empty())
{
auto annotation =
ch::calculateEBGNodeAnnotations(facade, packed_leg.begin(), packed_leg.end());
distances_table[row_index * number_of_targets + column_index] = annotation;
// check the direction of travel to figure out how to calculate the offset to/from
// the source/target
if (source_phantom.forward_segment_id.id == packed_leg.front())
{
// ............ <-- calculateEGBAnnotation returns distance from 0 to 3
// -->s <-- subtract offset to start at source
// ......... <-- want this distance as result
// entry 0---1---2---3--- <-- 3 is exit node
EdgeDistance offset = source_phantom.GetForwardDistance();
distances_table[row_index * number_of_targets + column_index] -= offset;
}
else if (source_phantom.reverse_segment_id.id == packed_leg.front())
{
// ............ <-- calculateEGBAnnotation returns distance from 0 to 3
// s<------- <-- subtract offset to start at source
// ... <-- want this distance
// entry 0---1---2---3 <-- 3 is exit node
EdgeDistance offset = source_phantom.GetReverseDistance();
distances_table[row_index * number_of_targets + column_index] -= offset;
}
if (target_phantom.forward_segment_id.id == packed_leg.back())
{
// ............ <-- calculateEGBAnnotation returns distance from 0 to 3
// ++>t <-- add offset to get to target
// ................ <-- want this distance as result
// entry 0---1---2---3--- <-- 3 is exit node
EdgeDistance offset = target_phantom.GetForwardDistance();
distances_table[row_index * number_of_targets + column_index] += offset;
}
else if (target_phantom.reverse_segment_id.id == packed_leg.back())
{
// ............ <-- calculateEGBAnnotation returns distance from 0 to 3
// <++t <-- add offset to get from target
// ................ <-- want this distance as result
// entry 0---1---2---3--- <-- 3 is exit node
EdgeDistance offset = target_phantom.GetReverseDistance();
distances_table[row_index * number_of_targets + column_index] += offset;
}
}
else
{
if (target_phantom.GetForwardDistance() > source_phantom.GetForwardDistance())
{
// ............ <-- calculateEGBAnnotation returns distance from 0 to 3
// ->s -->t <-- offsets
// --..........++++ <-- subtract source offset and add target offset
// .............. <-- want this distance as result
// entry 0---1---2---3--- <-- 3 is exit node
EdgeDistance offset =
target_phantom.GetForwardDistance() - source_phantom.GetForwardDistance();
distances_table[row_index * number_of_targets + column_index] += offset;
}
else
{
// ............ <-- calculateEGBAnnotation returns distance from 0 to 3
// s<--------<--t <-- GetReverseDistance() returns this offset
// ---.........++++ <-- subtract source offset and add target offset
// ............. <-- want this distance as result
// entry 0---1---2---3--- <-- 3 is exit node
EdgeDistance offset =
target_phantom.GetReverseDistance() - source_phantom.GetReverseDistance();
distances_table[row_index * number_of_targets + column_index] += offset;
}
}
packed_leg.clear();
}
}
template <>
std::pair<std::vector<EdgeDuration>, std::vector<EdgeDistance>>
manyToManySearch(SearchEngineData<ch::Algorithm> &engine_working_data,
const DataFacade<ch::Algorithm> &facade,
const std::vector<PhantomNode> &phantom_nodes,
const std::vector<std::size_t> &source_indices,
const std::vector<std::size_t> &target_indices,
const bool calculate_distance,
const bool calculate_duration)
{
(void)calculate_duration; // TODO: stub to use when computing durations become optional
const auto number_of_sources = source_indices.size();
const auto number_of_targets = target_indices.size();
const auto number_of_entries = number_of_sources * number_of_targets;
std::vector<EdgeWeight> weights_table(number_of_entries, INVALID_EDGE_WEIGHT);
std::vector<EdgeDuration> durations_table(number_of_entries, MAXIMAL_EDGE_DURATION);
std::vector<EdgeDistance> distances_table;
std::vector<NodeID> middle_nodes_table(number_of_entries, SPECIAL_NODEID);
std::vector<NodeBucket> search_space_with_buckets;
// Populate buckets with paths from all accessible nodes to destinations via backward searches
for (std::uint32_t column_idx = 0; column_idx < target_indices.size(); ++column_idx)
for (std::uint32_t column_index = 0; column_index < target_indices.size(); ++column_index)
{
const auto index = target_indices[column_idx];
const auto index = target_indices[column_index];
const auto &phantom = phantom_nodes[index];
engine_working_data.InitializeOrClearManyToManyThreadLocalStorage(
@ -184,7 +350,8 @@ std::vector<EdgeDuration> manyToManySearch(SearchEngineData<ch::Algorithm> &engi
// Explore search space
while (!query_heap.Empty())
{
backwardRoutingStep(facade, column_idx, query_heap, search_space_with_buckets, phantom);
backwardRoutingStep(
facade, column_index, query_heap, search_space_with_buckets, phantom);
}
}
@ -192,32 +359,49 @@ std::vector<EdgeDuration> manyToManySearch(SearchEngineData<ch::Algorithm> &engi
std::sort(search_space_with_buckets.begin(), search_space_with_buckets.end());
// Find shortest paths from sources to all accessible nodes
for (std::uint32_t row_idx = 0; row_idx < source_indices.size(); ++row_idx)
for (std::uint32_t row_index = 0; row_index < source_indices.size(); ++row_index)
{
const auto index = source_indices[row_idx];
const auto &phantom = phantom_nodes[index];
const auto source_index = source_indices[row_index];
const auto &source_phantom = phantom_nodes[source_index];
// Clear heap and insert source nodes
engine_working_data.InitializeOrClearManyToManyThreadLocalStorage(
facade.GetNumberOfNodes());
auto &query_heap = *(engine_working_data.many_to_many_heap);
insertSourceInHeap(query_heap, phantom);
insertSourceInHeap(query_heap, source_phantom);
// Explore search space
while (!query_heap.Empty())
{
forwardRoutingStep(facade,
row_idx,
row_index,
number_of_targets,
query_heap,
search_space_with_buckets,
weights_table,
durations_table,
phantom);
middle_nodes_table,
source_phantom);
}
if (calculate_distance)
{
distances_table.resize(number_of_entries, INVALID_EDGE_DISTANCE);
calculateDistances(query_heap,
facade,
phantom_nodes,
target_indices,
row_index,
source_index,
source_phantom,
number_of_targets,
search_space_with_buckets,
distances_table,
middle_nodes_table);
}
}
return durations_table;
return std::make_pair(durations_table, distances_table);
}
} // namespace routing_algorithms

View File

@ -207,14 +207,16 @@ void relaxOutgoingEdges(const DataFacade<mld::Algorithm> &facade,
// Unidirectional multi-layer Dijkstra search for 1-to-N and N-to-1 matrices
//
template <bool DIRECTION>
std::vector<EdgeDuration> oneToManySearch(SearchEngineData<Algorithm> &engine_working_data,
const DataFacade<Algorithm> &facade,
const std::vector<PhantomNode> &phantom_nodes,
std::size_t phantom_index,
const std::vector<std::size_t> &phantom_indices)
std::pair<std::vector<EdgeDuration>, std::vector<EdgeDistance>>
oneToManySearch(SearchEngineData<Algorithm> &engine_working_data,
const DataFacade<Algorithm> &facade,
const std::vector<PhantomNode> &phantom_nodes,
std::size_t phantom_index,
const std::vector<std::size_t> &phantom_indices)
{
std::vector<EdgeWeight> weights(phantom_indices.size(), INVALID_EDGE_WEIGHT);
std::vector<EdgeDuration> durations(phantom_indices.size(), MAXIMAL_EDGE_DURATION);
std::vector<EdgeDistance> distances(phantom_indices.size(), INVALID_EDGE_DISTANCE);
// Collect destination (source) nodes into a map
std::unordered_multimap<NodeID, std::tuple<std::size_t, EdgeWeight, EdgeDuration>>
@ -364,7 +366,7 @@ std::vector<EdgeDuration> oneToManySearch(SearchEngineData<Algorithm> &engine_wo
phantom_indices);
}
return durations;
return std::make_pair(durations, distances);
}
//
@ -432,9 +434,11 @@ void backwardRoutingStep(const DataFacade<Algorithm> &facade,
const auto node = query_heap.DeleteMin();
const auto target_weight = query_heap.GetKey(node);
const auto target_duration = query_heap.GetData(node).duration;
const auto parent = query_heap.GetData(node).parent;
// Store settled nodes in search space bucket
search_space_with_buckets.emplace_back(node, column_idx, target_weight, target_duration);
search_space_with_buckets.emplace_back(
node, parent, column_idx, target_weight, target_duration);
const auto &partition = facade.GetMultiLevelPartition();
const auto maximal_level = partition.GetNumberOfLevels() - 1;
@ -444,11 +448,12 @@ void backwardRoutingStep(const DataFacade<Algorithm> &facade,
}
template <bool DIRECTION>
std::vector<EdgeDuration> manyToManySearch(SearchEngineData<Algorithm> &engine_working_data,
const DataFacade<Algorithm> &facade,
const std::vector<PhantomNode> &phantom_nodes,
const std::vector<std::size_t> &source_indices,
const std::vector<std::size_t> &target_indices)
std::pair<std::vector<EdgeDuration>, std::vector<EdgeDistance>>
manyToManySearch(SearchEngineData<Algorithm> &engine_working_data,
const DataFacade<Algorithm> &facade,
const std::vector<PhantomNode> &phantom_nodes,
const std::vector<std::size_t> &source_indices,
const std::vector<std::size_t> &target_indices)
{
const auto number_of_sources = source_indices.size();
const auto number_of_targets = target_indices.size();
@ -456,6 +461,7 @@ std::vector<EdgeDuration> manyToManySearch(SearchEngineData<Algorithm> &engine_w
std::vector<EdgeWeight> weights_table(number_of_entries, INVALID_EDGE_WEIGHT);
std::vector<EdgeDuration> durations_table(number_of_entries, MAXIMAL_EDGE_DURATION);
std::vector<EdgeDistance> distances_table(number_of_entries, MAXIMAL_EDGE_DURATION);
std::vector<NodeBucket> search_space_with_buckets;
@ -516,7 +522,7 @@ std::vector<EdgeDuration> manyToManySearch(SearchEngineData<Algorithm> &engine_w
}
}
return durations_table;
return std::make_pair(durations_table, distances_table);
}
} // namespace mld
@ -534,12 +540,20 @@ std::vector<EdgeDuration> manyToManySearch(SearchEngineData<Algorithm> &engine_w
// then search is performed on a reversed graph with phantom nodes with flipped roles and
// returning a transposed matrix.
template <>
std::vector<EdgeDuration> manyToManySearch(SearchEngineData<mld::Algorithm> &engine_working_data,
const DataFacade<mld::Algorithm> &facade,
const std::vector<PhantomNode> &phantom_nodes,
const std::vector<std::size_t> &source_indices,
const std::vector<std::size_t> &target_indices)
std::pair<std::vector<EdgeDuration>, std::vector<EdgeDistance>>
manyToManySearch(SearchEngineData<mld::Algorithm> &engine_working_data,
const DataFacade<mld::Algorithm> &facade,
const std::vector<PhantomNode> &phantom_nodes,
const std::vector<std::size_t> &source_indices,
const std::vector<std::size_t> &target_indices,
const bool calculate_distance,
const bool calculate_duration)
{
(void)calculate_distance; // flag stub to use for calculating distances in matrix in mld in the
// future
(void)calculate_duration; // flag stub to use for calculating distances in matrix in mld in the
// future
if (source_indices.size() == 1)
{ // TODO: check if target_indices.size() == 1 and do a bi-directional search
return mld::oneToManySearch<FORWARD_DIRECTION>(

View File

@ -59,6 +59,24 @@ void retrievePackedPathFromSingleHeap(const SearchEngineData<Algorithm>::QueryHe
}
}
void retrievePackedPathFromSingleManyToManyHeap(
const SearchEngineData<Algorithm>::ManyToManyQueryHeap &search_heap,
const NodeID middle_node_id,
std::vector<NodeID> &packed_path)
{
NodeID current_node_id = middle_node_id;
// all initial nodes will have itself as parent, or a node not in the heap
// in case of a core search heap. We need a distinction between core entry nodes
// and start nodes since otherwise start node specific code that assumes
// node == node.parent (e.g. the loop code) might get actived.
while (current_node_id != search_heap.GetData(current_node_id).parent &&
search_heap.WasInserted(search_heap.GetData(current_node_id).parent))
{
current_node_id = search_heap.GetData(current_node_id).parent;
packed_path.emplace_back(current_node_id);
}
}
// assumes that heaps are already setup correctly.
// ATTENTION: This only works if no additional offset is supplied next to the Phantom Node
// Offsets.

View File

@ -101,7 +101,6 @@ std::vector<TurnData> generateTurns(const datafacade &facade,
// w
// uv is the "approach"
// vw is the "exit"
// Look at every node in the directed graph we created
for (const auto &startnode : sorted_startnodes)
{

View File

@ -263,7 +263,7 @@ NAN_METHOD(Engine::nearest) //
// clang-format off
/**
* Computes duration tables for the given locations. Allows for both symmetric and asymmetric
* Computes duration and distance tables for the given locations. Allows for both symmetric and asymmetric
* tables.
*
* @name table
@ -299,6 +299,7 @@ NAN_METHOD(Engine::nearest) //
* };
* osrm.table(options, function(err, response) {
* console.log(response.durations); // array of arrays, matrix in row-major order
* console.log(response.distances); // array of arrays, matrix in row-major order
* console.log(response.sources); // array of Waypoint objects
* console.log(response.destinations); // array of Waypoint objects
* });

View File

@ -6,178 +6,187 @@ var three_test_coordinates = require('./constants').three_test_coordinates;
var two_test_coordinates = require('./constants').two_test_coordinates;
test('table: distance table in Monaco', function(assert) {
assert.plan(11);
var osrm = new OSRM(data_path);
var options = {
coordinates: [three_test_coordinates[0], three_test_coordinates[1]]
};
osrm.table(options, function(err, table) {
assert.ifError(err);
assert.ok(Array.isArray(table.durations), 'result must be an array');
var row_count = table.durations.length;
for (var i = 0; i < row_count; ++i) {
var column = table.durations[i];
var column_count = column.length;
assert.equal(row_count, column_count);
for (var j = 0; j < column_count; ++j) {
if (i == j) {
// check that diagonal is zero
assert.equal(0, column[j], 'diagonal must be zero');
} else {
// everything else is non-zero
assert.notEqual(0, column[j], 'other entries must be non-zero');
// and finite (not nan, inf etc.)
assert.ok(Number.isFinite(column[j]), 'distance is finite number');
var tables = ['distances', 'durations'];
tables.forEach(function(annotation) {
test('table: ' + annotation + ' table in Monaco', function(assert) {
assert.plan(11);
var osrm = new OSRM(data_path);
var options = {
coordinates: [three_test_coordinates[0], three_test_coordinates[1]],
annotations: [annotation.slice(0,-1)]
};
osrm.table(options, function(err, table) {
assert.ifError(err);
assert.ok(Array.isArray(table[annotation]), 'result must be an array');
var row_count = table[annotation].length;
for (var i = 0; i < row_count; ++i) {
var column = table[annotation][i];
var column_count = column.length;
assert.equal(row_count, column_count);
for (var j = 0; j < column_count; ++j) {
if (i == j) {
// check that diagonal is zero
assert.equal(0, column[j], 'diagonal must be zero');
} else {
// everything else is non-zero
assert.notEqual(0, column[j], 'other entries must be non-zero');
// and finite (not nan, inf etc.)
assert.ok(Number.isFinite(column[j]), 'distance is finite number');
}
}
}
}
assert.equal(options.coordinates.length, row_count);
assert.equal(options.coordinates.length, row_count);
});
});
});
test('table: distance table in Monaco with sources/destinations', function(assert) {
assert.plan(7);
var osrm = new OSRM(data_path);
var options = {
coordinates: [three_test_coordinates[0], three_test_coordinates[1]],
sources: [0],
destinations: [0,1]
};
osrm.table(options, function(err, table) {
assert.ifError(err);
assert.ok(Array.isArray(table.durations), 'result must be an array');
var row_count = table.durations.length;
for (var i = 0; i < row_count; ++i) {
var column = table.durations[i];
var column_count = column.length;
assert.equal(options.destinations.length, column_count);
for (var j = 0; j < column_count; ++j) {
if (i == j) {
// check that diagonal is zero
assert.equal(0, column[j], 'diagonal must be zero');
} else {
// everything else is non-zero
assert.notEqual(0, column[j], 'other entries must be non-zero');
// and finite (not nan, inf etc.)
assert.ok(Number.isFinite(column[j]), 'distance is finite number');
test('table: ' + annotation + ' table in Monaco with sources/destinations', function(assert) {
assert.plan(7);
var osrm = new OSRM(data_path);
var options = {
coordinates: [three_test_coordinates[0], three_test_coordinates[1]],
sources: [0],
destinations: [0,1],
annotations: [annotation.slice(0,-1)]
};
osrm.table(options, function(err, table) {
assert.ifError(err);
assert.ok(Array.isArray(table[annotation]), 'result must be an array');
var row_count = table[annotation].length;
for (var i = 0; i < row_count; ++i) {
var column = table[annotation][i];
var column_count = column.length;
assert.equal(options.destinations.length, column_count);
for (var j = 0; j < column_count; ++j) {
if (i == j) {
// check that diagonal is zero
assert.equal(0, column[j], 'diagonal must be zero');
} else {
// everything else is non-zero
assert.notEqual(0, column[j], 'other entries must be non-zero');
// and finite (not nan, inf etc.)
assert.ok(Number.isFinite(column[j]), 'distance is finite number');
}
}
}
}
assert.equal(options.sources.length, row_count);
});
});
test('table: throws on invalid arguments', function(assert) {
assert.plan(14);
var osrm = new OSRM(data_path);
var options = {};
assert.throws(function() { osrm.table(options); },
/Two arguments required/);
options.coordinates = null;
assert.throws(function() { osrm.table(options, function() {}); },
/Coordinates must be an array of \(lon\/lat\) pairs/);
options.coordinates = [three_test_coordinates[0]];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/At least two coordinates must be provided/);
options.coordinates = three_test_coordinates[0];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Coordinates must be an array of \(lon\/lat\) pairs/);
options.coordinates = [three_test_coordinates[0][0], three_test_coordinates[0][1]];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Coordinates must be an array of \(lon\/lat\) pairs/);
options.coordinates = two_test_coordinates;
options.sources = true;
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Sources must be an array of indices \(or undefined\)/);
options.sources = [0, 4];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Source indices must be less than or equal to the number of coordinates/);
options.sources = [0.3, 1.1];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Source must be an integer/);
options.destinations = true;
delete options.sources;
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Destinations must be an array of indices \(or undefined\)/);
options.destinations = [0, 4];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Destination indices must be less than or equal to the number of coordinates/);
options.destinations = [0.3, 1.1];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Destination must be an integer/);
// does not throw: the following two have been changed in OSRM v5
options.sources = [0, 1];
delete options.destinations;
assert.doesNotThrow(function() { osrm.table(options, function(err, response) {}) },
/Both sources and destinations need to be specified/);
options.destinations = [0, 1];
assert.doesNotThrow(function() { osrm.table(options, function(err, response) {}) },
/You can either specify sources and destinations, or coordinates/);
assert.throws(function() { osrm.route({coordinates: two_test_coordinates, generate_hints: null}, function(err, route) {}) },
/generate_hints must be of type Boolean/);
});
test('table: throws on invalid arguments', function(assert) {
assert.plan(1);
var osrm = new OSRM(data_path);
assert.throws(function() { osrm.table(null, function() {}); },
/First arg must be an object/);
});
test('table: distance table in Monaco with hints', function(assert) {
assert.plan(5);
var osrm = new OSRM(data_path);
var options = {
coordinates: two_test_coordinates,
generate_hints: true // true is default but be explicit here
};
osrm.table(options, function(err, table) {
console.log(table);
assert.ifError(err);
function assertHasHints(waypoint) {
assert.notStrictEqual(waypoint.hint, undefined);
}
table.sources.map(assertHasHints);
table.destinations.map(assertHasHints);
});
});
test('table: distance table in Monaco without hints', function(assert) {
assert.plan(5);
var osrm = new OSRM(data_path);
var options = {
coordinates: two_test_coordinates,
generate_hints: false // true is default
};
osrm.table(options, function(err, table) {
assert.ifError(err);
function assertHasNoHints(waypoint) {
assert.strictEqual(waypoint.hint, undefined);
}
table.sources.map(assertHasNoHints);
table.destinations.map(assertHasNoHints);
});
});
test('table: table in Monaco without motorways', function(assert) {
assert.plan(2);
var osrm = new OSRM({path: mld_data_path, algorithm: 'MLD'});
var options = {
coordinates: two_test_coordinates,
exclude: ['motorway']
};
osrm.table(options, function(err, response) {
assert.ifError(err);
assert.equal(response.durations.length, 2);
assert.equal(options.sources.length, row_count);
});
});
test('table: ' + annotation + ' throws on invalid arguments', function(assert) {
assert.plan(14);
var osrm = new OSRM(data_path);
var options = {annotations: [annotation.slice(0,-1)]};
assert.throws(function() { osrm.table(options); },
/Two arguments required/);
options.coordinates = null;
assert.throws(function() { osrm.table(options, function() {}); },
/Coordinates must be an array of \(lon\/lat\) pairs/);
options.coordinates = [three_test_coordinates[0]];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/At least two coordinates must be provided/);
options.coordinates = three_test_coordinates[0];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Coordinates must be an array of \(lon\/lat\) pairs/);
options.coordinates = [three_test_coordinates[0][0], three_test_coordinates[0][1]];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Coordinates must be an array of \(lon\/lat\) pairs/);
options.coordinates = two_test_coordinates;
options.sources = true;
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Sources must be an array of indices \(or undefined\)/);
options.sources = [0, 4];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Source indices must be less than or equal to the number of coordinates/);
options.sources = [0.3, 1.1];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Source must be an integer/);
options.destinations = true;
delete options.sources;
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Destinations must be an array of indices \(or undefined\)/);
options.destinations = [0, 4];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Destination indices must be less than or equal to the number of coordinates/);
options.destinations = [0.3, 1.1];
assert.throws(function() { osrm.table(options, function(err, response) {}) },
/Destination must be an integer/);
// does not throw: the following two have been changed in OSRM v5
options.sources = [0, 1];
delete options.destinations;
assert.doesNotThrow(function() { osrm.table(options, function(err, response) {}) },
/Both sources and destinations need to be specified/);
options.destinations = [0, 1];
assert.doesNotThrow(function() { osrm.table(options, function(err, response) {}) },
/You can either specify sources and destinations, or coordinates/);
assert.throws(function() { osrm.route({coordinates: two_test_coordinates, generate_hints: null}, function(err, route) {}) },
/generate_hints must be of type Boolean/);
});
test('table: throws on invalid arguments', function(assert) {
assert.plan(1);
var osrm = new OSRM(data_path);
assert.throws(function() { osrm.table(null, function() {}); },
/First arg must be an object/);
});
test('table: ' + annotation + ' table in Monaco with hints', function(assert) {
assert.plan(5);
var osrm = new OSRM(data_path);
var options = {
coordinates: two_test_coordinates,
generate_hints: true, // true is default but be explicit here
annotations: [annotation.slice(0,-1)]
};
osrm.table(options, function(err, table) {
console.log(table);
assert.ifError(err);
function assertHasHints(waypoint) {
assert.notStrictEqual(waypoint.hint, undefined);
}
table.sources.map(assertHasHints);
table.destinations.map(assertHasHints);
});
});
test('table: ' + annotation + ' table in Monaco without hints', function(assert) {
assert.plan(5);
var osrm = new OSRM(data_path);
var options = {
coordinates: two_test_coordinates,
generate_hints: false, // true is default
annotations: [annotation.slice(0,-1)]
};
osrm.table(options, function(err, table) {
assert.ifError(err);
function assertHasNoHints(waypoint) {
assert.strictEqual(waypoint.hint, undefined);
}
table.sources.map(assertHasNoHints);
table.destinations.map(assertHasNoHints);
});
});
test('table: ' + annotation + ' table in Monaco without motorways', function(assert) {
assert.plan(2);
var osrm = new OSRM({path: mld_data_path, algorithm: 'MLD'});
var options = {
coordinates: two_test_coordinates,
exclude: ['motorway'],
annotations: [annotation.slice(0,-1)]
};
osrm.table(options, function(err, response) {
assert.ifError(err);
assert.equal(response[annotation].length, 2);
});
});
});

View File

@ -27,6 +27,7 @@ BOOST_AUTO_TEST_CASE(test_table_three_coords_one_source_one_dest_matrix)
params.coordinates.push_back(get_dummy_location());
params.sources.push_back(0);
params.destinations.push_back(2);
params.annotations = TableParameters::AnnotationsType::All;
json::Object result;
@ -46,6 +47,18 @@ BOOST_AUTO_TEST_CASE(test_table_three_coords_one_source_one_dest_matrix)
BOOST_CHECK_EQUAL(durations_matrix.size(),
params.sources.size() * params.destinations.size());
}
// check that returned distances error is expected size and proportions
// this test expects a 1x1 matrix
const auto &distances_array = result.values.at("distances").get<json::Array>().values;
BOOST_CHECK_EQUAL(distances_array.size(), params.sources.size());
for (unsigned int i = 0; i < distances_array.size(); i++)
{
const auto distances_matrix = distances_array[i].get<json::Array>().values;
BOOST_CHECK_EQUAL(distances_matrix.size(),
params.sources.size() * params.destinations.size());
}
// check destinations array of waypoint objects
const auto &destinations_array = result.values.at("destinations").get<json::Array>().values;
BOOST_CHECK_EQUAL(destinations_array.size(), params.destinations.size());
@ -73,7 +86,6 @@ BOOST_AUTO_TEST_CASE(test_table_three_coords_one_source_matrix)
params.coordinates.push_back(get_dummy_location());
params.coordinates.push_back(get_dummy_location());
params.sources.push_back(0);
json::Object result;
const auto rc = osrm.Table(params, result);
@ -119,6 +131,7 @@ BOOST_AUTO_TEST_CASE(test_table_three_coordinates_matrix)
params.coordinates.push_back(get_dummy_location());
params.coordinates.push_back(get_dummy_location());
params.coordinates.push_back(get_dummy_location());
params.annotations = TableParameters::AnnotationsType::Duration;
json::Object result;

View File

@ -56,7 +56,9 @@ class MockBaseDataFacade : public engine::datafacade::BaseDataFacade
}
NodeForwardRange GetUncompressedForwardGeometry(const EdgeID /* id */) const override
{
return {};
static NodeID data[] = {0, 1, 2, 3};
static extractor::SegmentDataView::SegmentNodeVector nodes(data, 4);
return boost::make_iterator_range(nodes.cbegin(), nodes.cend());
}
NodeReverseRange GetUncompressedReverseGeometry(const EdgeID id) const override
{
@ -73,7 +75,6 @@ class MockBaseDataFacade : public engine::datafacade::BaseDataFacade
{
return WeightReverseRange(GetUncompressedForwardWeights(id));
}
DurationForwardRange GetUncompressedForwardDurations(const EdgeID /*id*/) const override
{
static std::uint64_t data[] = {1, 2, 3};

View File

@ -86,11 +86,13 @@ BOOST_AUTO_TEST_CASE(invalid_table_urls)
testInvalidOptions<TableParameters>("1,2;3,4?sources=1&destinations=1&bla=foo"), 32UL);
BOOST_CHECK_EQUAL(testInvalidOptions<TableParameters>("1,2;3,4?sources=foo"), 16UL);
BOOST_CHECK_EQUAL(testInvalidOptions<TableParameters>("1,2;3,4?destinations=foo"), 21UL);
BOOST_CHECK_EQUAL(
testInvalidOptions<TableParameters>("1,2;3,4?sources=all&destinations=all&annotations=bla"),
49UL);
}
BOOST_AUTO_TEST_CASE(valid_route_hint)
{
engine::PhantomNode reference_node;
reference_node.input_location =
util::Coordinate(util::FloatLongitude{7.432251}, util::FloatLatitude{43.745995});
@ -284,6 +286,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls)
{util::FloatLongitude{3}, util::FloatLatitude{4}},
{util::FloatLongitude{5}, util::FloatLatitude{6}},
{util::FloatLongitude{7}, util::FloatLatitude{8}}};
engine::PhantomNode phantom_3;
phantom_3.input_location = coords_3[0];
engine::PhantomNode phantom_4;
@ -379,11 +382,11 @@ BOOST_AUTO_TEST_CASE(valid_route_urls)
"overview=simplified&annotations=duration,weight,nodes");
BOOST_CHECK(result_16);
BOOST_CHECK_EQUAL(reference_16.geometries, result_16->geometries);
BOOST_CHECK_EQUAL(
static_cast<bool>(result_2->annotations_type & (RouteParameters::AnnotationsType::Weight |
RouteParameters::AnnotationsType::Duration |
RouteParameters::AnnotationsType::Nodes)),
true);
BOOST_CHECK_EQUAL(static_cast<bool>(result_16->annotations_type &
(RouteParameters::AnnotationsType::Weight |
RouteParameters::AnnotationsType::Duration |
RouteParameters::AnnotationsType::Nodes)),
true);
BOOST_CHECK_EQUAL(result_16->annotations, true);
// parse all annotations correctly
@ -523,6 +526,43 @@ BOOST_AUTO_TEST_CASE(valid_table_urls)
CHECK_EQUAL_RANGE(reference_1.radiuses, result_3->radiuses);
CHECK_EQUAL_RANGE(reference_1.approaches, result_3->approaches);
CHECK_EQUAL_RANGE(reference_1.coordinates, result_3->coordinates);
TableParameters reference_4{};
reference_4.coordinates = coords_1;
auto result_4 = parseParameters<TableParameters>(
"1,2;3,4?sources=all&destinations=all&annotations=duration");
BOOST_CHECK(result_4);
BOOST_CHECK_EQUAL(result_4->annotations & (TableParameters::AnnotationsType::Distance |
TableParameters::AnnotationsType::Duration),
true);
CHECK_EQUAL_RANGE(reference_4.sources, result_4->sources);
CHECK_EQUAL_RANGE(reference_4.destinations, result_4->destinations);
TableParameters reference_5{};
reference_5.coordinates = coords_1;
auto result_5 = parseParameters<TableParameters>(
"1,2;3,4?sources=all&destinations=all&annotations=duration");
BOOST_CHECK(result_5);
BOOST_CHECK_EQUAL(result_5->annotations & TableParameters::AnnotationsType::Duration, true);
CHECK_EQUAL_RANGE(reference_5.sources, result_5->sources);
CHECK_EQUAL_RANGE(reference_5.destinations, result_5->destinations);
TableParameters reference_6{};
reference_6.coordinates = coords_1;
auto result_6 = parseParameters<TableParameters>(
"1,2;3,4?sources=all&destinations=all&annotations=distance");
BOOST_CHECK(result_6);
BOOST_CHECK_EQUAL(result_6->annotations & TableParameters::AnnotationsType::Distance, true);
CHECK_EQUAL_RANGE(reference_6.sources, result_6->sources);
CHECK_EQUAL_RANGE(reference_6.destinations, result_6->destinations);
TableParameters reference_7{};
reference_7.coordinates = coords_1;
auto result_7 = parseParameters<TableParameters>("1,2;3,4?annotations=distance");
BOOST_CHECK(result_7);
BOOST_CHECK_EQUAL(result_7->annotations & TableParameters::AnnotationsType::Distance, true);
CHECK_EQUAL_RANGE(reference_7.sources, result_7->sources);
CHECK_EQUAL_RANGE(reference_7.destinations, result_7->destinations);
}
BOOST_AUTO_TEST_CASE(valid_match_urls)

View File

@ -220,6 +220,7 @@ BOOST_AUTO_TEST_CASE(packed_weights_container_with_type_erasure)
auto forward = boost::make_iterator_range(vector.begin() + 1, vector.begin() + 6);
auto forward_any = WeightsAnyRange(forward.begin(), forward.end());
CHECK_EQUAL_RANGE(forward, 1, 2, 3, 4, 5);
CHECK_EQUAL_RANGE(forward_any, 1, 2, 3, 4, 5);
@ -243,11 +244,13 @@ BOOST_AUTO_TEST_CASE(packed_weights_view_with_type_erasure)
auto forward = boost::make_iterator_range(view.begin() + 1, view.begin() + 4);
auto forward_any = WeightsAnyRange(forward.begin(), forward.end());
CHECK_EQUAL_RANGE(forward, 1, 2, 3);
CHECK_EQUAL_RANGE(forward_any, 1, 2, 3);
auto reverse = boost::adaptors::reverse(forward);
auto reverse_any = WeightsAnyRange(reverse);
CHECK_EQUAL_RANGE(reverse, 3, 2, 1);
CHECK_EQUAL_RANGE(reverse_any, 3, 2, 1);
}