From 14860b62e973467dacb36459c4b1eca86bf02b34 Mon Sep 17 00:00:00 2001 From: Kajari Ghosh Date: Fri, 20 Apr 2018 18:18:55 -0400 Subject: [PATCH] Unpack paths and return total distance in matrix plugin for CH (#4990) --- CHANGELOG.md | 3 + README.md | 2 +- cucumber.js | 2 +- docs/http.md | 123 ++++- docs/nodejs/api.md | 6 +- features/step_definitions/distance_matrix.js | 136 ++--- features/support/hooks.js | 2 +- features/testbot/distance_matrix.feature | 453 +++++++++------- features/testbot/duration_matrix.feature | 490 ++++++++++++++++++ include/engine/api/table_api.hpp | 56 +- include/engine/api/table_parameters.hpp | 42 +- .../contiguous_internalmem_datafacade.hpp | 3 - include/engine/datafacade/datafacade_base.hpp | 1 - include/engine/geospatial_query.hpp | 37 +- include/engine/hint.hpp | 4 +- include/engine/phantom_node.hpp | 45 +- include/engine/routing_algorithms.hpp | 31 +- .../routing_algorithms/many_to_many.hpp | 46 +- .../routing_algorithms/routing_base.hpp | 17 + .../routing_algorithms/routing_base_ch.hpp | 105 ++++ include/nodejs/node_osrm_support.hpp | 38 ++ .../server/api/table_parameter_grammar.hpp | 27 +- include/util/typedefs.hpp | 2 + profiles/testbot.lua | 2 +- src/engine/plugins/table.cpp | 13 +- src/engine/plugins/tile.cpp | 2 - src/engine/plugins/trip.cpp | 45 +- .../direct_shortest_path.cpp | 1 - .../routing_algorithms/many_to_many_ch.cpp | 230 +++++++- .../routing_algorithms/many_to_many_mld.cpp | 50 +- .../routing_algorithms/routing_base_ch.cpp | 18 + src/engine/routing_algorithms/tile_turns.cpp | 1 - src/nodejs/node_osrm.cpp | 3 +- test/nodejs/table.js | 325 ++++++------ unit_tests/library/table.cpp | 15 +- unit_tests/mocks/mock_datafacade.hpp | 5 +- unit_tests/server/parameters_parser.cpp | 52 +- unit_tests/util/packed_vector.cpp | 3 + 38 files changed, 1892 insertions(+), 544 deletions(-) create mode 100644 features/testbot/duration_matrix.feature diff --git a/CHANGELOG.md b/CHANGELOG.md index 51b1efd2565..eda0d905050 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/README.md b/README.md index 4afc7836714..4f3540e0f96 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cucumber.js b/cucumber.js index b0276ceed45..288e3ba5aea 100644 --- a/cucumber.js +++ b/cucumber.js @@ -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' }; diff --git a/docs/http.md b/docs/http.md index b14b0a341db..4eb9b60d474 100644 --- a/docs/http.md +++ b/docs/http.md @@ -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. diff --git a/docs/nodejs/api.md b/docs/nodejs/api.md index eec9ab67de4..8e864ce1ad1 100644 --- a/docs/nodejs/api.md +++ b/docs/nodejs/api.md @@ -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 }); diff --git a/features/step_definitions/distance_matrix.js b/features/step_definitions/distance_matrix.js index aab1f606458..fde80679f5f 100644 --- a/features/step_definitions/distance_matrix.js +++ b/features/step_definitions/distance_matrix.js @@ -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' }); - }); - } 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' }); +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; }); - } - - 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); }); }); -}; +} diff --git a/features/support/hooks.js b/features/support/hooks.js index e58b34bfa22..7bd8116cb9e 100644 --- a/features/support/hooks.js +++ b/features/support/hooks.js @@ -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) => { diff --git a/features/testbot/distance_matrix.feature b/features/testbot/distance_matrix.feature index 18b400514ef..46f60d7de1b 100644 --- a/features/testbot/distance_matrix.feature +++ b/features/testbot/distance_matrix.feature @@ -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,57 +16,131 @@ 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 distance matrix of minimal network with toll exclude + Given the query options + | exclude | toll | - Scenario: Testbot - Travel time matrix with different way speeds Given the node map """ - a b c d + a b + c d """ And the ways - | nodes | highway | - | ab | primary | - | bc | secondary | - | cd | tertiary | + | 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 | - 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 with fuzzy match Given the node map """ a b + c d """ And the ways - | nodes | - | ab | + | 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 | - 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 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 + """ + + And the ways + | nodes | highway | + | ab | primary | + | bc | secondary | + | cd | tertiary | + + 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 distance matrix I should get + | | a | b | c | d | + | a | 0 | 100 | 200 | 299.9 | + + When I request a travel distance matrix I should get + | | a | + | a | 0 | + | b | 100 | + | c | 200 | + | d | 299.9 | + + 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 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 + 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 distance matrix I should get + | | a | + | a | 0 | + | b | 100 | + | e | 200 | + | f | 299.9 | + + 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 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 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 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 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 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 | - - Scenario: Testbot - Travel time matrix with loops + 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 | + + 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 | - - 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 + | nodes | + | abcd | + | ce | + + 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 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 | - - - 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 + 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 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 | 20s | 20 | 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 | + | 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 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 | diff --git a/features/testbot/duration_matrix.feature b/features/testbot/duration_matrix.feature new file mode 100644 index 00000000000..f93ea793608 --- /dev/null +++ b/features/testbot/duration_matrix.feature @@ -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 | diff --git a/include/engine/api/table_api.hpp b/include/engine/api/table_api.hpp index 2ff667404ab..7f8cb770615 100644 --- a/include/engine/api/table_api.hpp +++ b/include/engine/api/table_api.hpp @@ -36,9 +36,10 @@ class TableAPI final : public BaseAPI { } - virtual void MakeResponse(const std::vector &durations, - const std::vector &phantoms, - util::json::Object &response) const + virtual void + MakeResponse(const std::pair, std::vector> &tables, + const std::vector &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 &values, - std::size_t number_of_rows, - std::size_t number_of_columns) const + virtual util::json::Array MakeDurationTable(const std::vector &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(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 &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(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 ¶meters; }; diff --git a/include/engine/api/table_parameters.hpp b/include/engine/api/table_parameters.hpp index 58d936c7347..0e4cb12aea5 100644 --- a/include/engine/api/table_parameters.hpp +++ b/include/engine/api/table_parameters.hpp @@ -60,6 +60,16 @@ struct TableParameters : public BaseParameters std::vector sources; std::vector destinations; + enum class AnnotationsType + { + None = 0, + Duration = 0x01, + Distance = 0x02, + All = Duration | Distance + }; + + AnnotationsType annotations = AnnotationsType::Duration; + TableParameters() = default; template TableParameters(std::vector sources_, @@ -70,6 +80,16 @@ struct TableParameters : public BaseParameters { } + template + TableParameters(std::vector sources_, + std::vector destinations_, + const AnnotationsType annotations_, + Args... args_) + : BaseParameters{std::forward(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( + static_cast>(lhs) & + static_cast>(rhs)); +} + +inline TableParameters::AnnotationsType operator|(TableParameters::AnnotationsType lhs, + TableParameters::AnnotationsType rhs) +{ + return (TableParameters::AnnotationsType)( + static_cast>(lhs) | + static_cast>(rhs)); +} + +inline TableParameters::AnnotationsType operator|=(TableParameters::AnnotationsType lhs, + TableParameters::AnnotationsType rhs) +{ + return lhs = lhs | rhs; +} } } } diff --git a/include/engine/datafacade/contiguous_internalmem_datafacade.hpp b/include/engine/datafacade/contiguous_internalmem_datafacade.hpp index 846a1a04c77..2c537bb7ce5 100644 --- a/include/engine/datafacade/contiguous_internalmem_datafacade.hpp +++ b/include/engine/datafacade/contiguous_internalmem_datafacade.hpp @@ -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 const std::size_t exclude_index) : ContiguousInternalMemoryDataFacadeBase(allocator, metric_name, exclude_index), ContiguousInternalMemoryAlgorithmDataFacade(allocator, metric_name, exclude_index) - { } }; @@ -720,7 +718,6 @@ class ContiguousInternalMemoryDataFacade final const std::size_t exclude_index) : ContiguousInternalMemoryDataFacadeBase(allocator, metric_name, exclude_index), ContiguousInternalMemoryAlgorithmDataFacade(allocator, metric_name, exclude_index) - { } }; diff --git a/include/engine/datafacade/datafacade_base.hpp b/include/engine/datafacade/datafacade_base.hpp index beb71674ee0..f5e4e01af19 100644 --- a/include/engine/datafacade/datafacade_base.hpp +++ b/include/engine/datafacade/datafacade_base.hpp @@ -33,7 +33,6 @@ #include #include - #include #include diff --git a/include/engine/geospatial_query.hpp b/include/engine/geospatial_query.hpp index b9a709d0cb7..cd53f34f469 100644 --- a/include/engine/geospatial_query.hpp +++ b/include/engine/geospatial_query.hpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -447,6 +448,8 @@ template 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 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 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 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, diff --git a/include/engine/hint.hpp b/include/engine/hint.hpp index 7334b673796..3ab69b928e0 100644 --- a/include/engine/hint.hpp +++ b/include/engine/hint.hpp @@ -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"); } diff --git a/include/engine/phantom_node.hpp b/include/engine/phantom_node.hpp index 9357c009e10..3cb0b959369 100644 --- a/include/engine/phantom_node.hpp +++ b/include/engine/phantom_node.hpp @@ -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; diff --git a/include/engine/routing_algorithms.hpp b/include/engine/routing_algorithms.hpp index dd8bcc8dde5..b2a3d9a3f53 100644 --- a/include/engine/routing_algorithms.hpp +++ b/include/engine/routing_algorithms.hpp @@ -30,10 +30,12 @@ class RoutingAlgorithmsInterface virtual InternalRouteResult DirectShortestPathSearch(const PhantomNodes &phantom_node_pair) const = 0; - virtual std::vector + virtual std::pair, std::vector> ManyToManySearch(const std::vector &phantom_nodes, const std::vector &source_indices, - const std::vector &target_indices) const = 0; + const std::vector &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 class RoutingAlgorithms final : public RoutingAlgo InternalRouteResult DirectShortestPathSearch(const PhantomNodes &phantom_nodes) const final override; - std::vector + virtual std::pair, std::vector> ManyToManySearch(const std::vector &phantom_nodes, const std::vector &source_indices, - const std::vector &target_indices) const final override; + const std::vector &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::MapMatc } template -std::vector RoutingAlgorithms::ManyToManySearch( - const std::vector &phantom_nodes, - const std::vector &_source_indices, - const std::vector &_target_indices) const +std::pair, std::vector> +RoutingAlgorithms::ManyToManySearch(const std::vector &phantom_nodes, + const std::vector &_source_indices, + const std::vector &_target_indices, + const bool calculate_distance, + const bool calculate_duration) const { BOOST_ASSERT(!phantom_nodes.empty()); @@ -205,8 +211,13 @@ std::vector RoutingAlgorithms::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 diff --git a/include/engine/routing_algorithms/many_to_many.hpp b/include/engine/routing_algorithms/many_to_many.hpp index f4d21884645..bef14874c6b 100644 --- a/include/engine/routing_algorithms/many_to_many.hpp +++ b/include/engine/routing_algorithms/many_to_many.hpp @@ -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 -std::vector manyToManySearch(SearchEngineData &engine_working_data, - const DataFacade &facade, - const std::vector &phantom_nodes, - const std::vector &source_indices, - const std::vector &target_indices); +std::pair, std::vector> +manyToManySearch(SearchEngineData &engine_working_data, + const DataFacade &facade, + const std::vector &phantom_nodes, + const std::vector &source_indices, + const std::vector &target_indices, + const bool calculate_distance, + const bool calculate_duration); } // namespace routing_algorithms } // namespace engine diff --git a/include/engine/routing_algorithms/routing_base.hpp b/include/engine/routing_algorithms/routing_base.hpp index 90e5e73e2a8..000f1b14d23 100644 --- a/include/engine/routing_algorithms/routing_base.hpp +++ b/include/engine/routing_algorithms/routing_base.hpp @@ -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 &facade, return raw_route_data; } +template 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 diff --git a/include/engine/routing_algorithms/routing_base_ch.hpp b/include/engine/routing_algorithms/routing_base_ch.hpp index 5ecbc6b2928..051b5f8c4a6 100644 --- a/include/engine/routing_algorithms/routing_base_ch.hpp +++ b/include/engine/routing_algorithms/routing_base_ch.hpp @@ -288,6 +288,106 @@ void unpackPath(const DataFacade &facade, } } +template +EdgeDistance calculateEBGNodeAnnotations(const DataFacade &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> recursion_stack; + std::stack 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 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::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 void unpackPath(const FacadeT &facade, RandomIter packed_path_begin, @@ -340,6 +440,11 @@ void retrievePackedPathFromSingleHeap(const SearchEngineData::QueryHe const NodeID middle_node_id, std::vector &packed_path); +void retrievePackedPathFromSingleManyToManyHeap( + const SearchEngineData::ManyToManyQueryHeap &search_heap, + const NodeID middle_node_id, + std::vector &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. diff --git a/include/nodejs/node_osrm_support.hpp b/include/nodejs/node_osrm_support.hpp index 2bad0a205cd..72bee7ce706 100644 --- a/include/nodejs/node_osrm_support.hpp +++ b/include/nodejs/node_osrm_support.hpp @@ -1064,6 +1064,44 @@ argumentsToTableParameter(const Nan::FunctionCallbackInfo &args, } } + if (obj->Has(Nan::New("annotations").ToLocalChecked())) + { + v8::Local 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 annotations_array = v8::Local::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; } diff --git a/include/server/api/table_parameter_grammar.hpp b/include/server/api/table_parameter_grammar.hpp index 5a97c8e3d40..e922f965b10 100644 --- a/include/server/api/table_parameter_grammar.hpp +++ b/include/server/api/table_parameter_grammar.hpp @@ -22,11 +22,11 @@ namespace qi = boost::spirit::qi; template -struct TableParametersGrammar final : public BaseParametersGrammar +struct TableParametersGrammar : public BaseParametersGrammar { using BaseGrammar = BaseParametersGrammar; - TableParametersGrammar() : BaseGrammar(root_rule) + TableParametersGrammar() : TableParametersGrammar(root_rule) { #ifdef BOOST_HAS_LONG_LONG if (std::is_same::value) @@ -51,15 +51,36 @@ struct TableParametersGrammar final : public BaseParametersGrammar -qi::lit(".json") > - -('?' > (table_rule(qi::_r1) | BaseGrammar::base_rule(qi::_r1)) % '&'); + -('?' > (table_rule(qi::_r1) | base_rule(qi::_r1)) % '&'); } + TableParametersGrammar(qi::rule &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 base_rule; + private: qi::rule root_rule; qi::rule table_rule; qi::rule sources_rule; qi::rule destinations_rule; qi::rule size_t_; + qi::symbols annotations; }; } } diff --git a/include/util/typedefs.hpp b/include/util/typedefs.hpp index 9b8ecc82bbc..f0104e7b258 100644 --- a/include/util/typedefs.hpp +++ b/include/util/typedefs.hpp @@ -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::max(); static const EdgeDuration MAXIMAL_EDGE_DURATION = std::numeric_limits::max(); static const TurnPenalty INVALID_TURN_PENALTY = std::numeric_limits::max(); +static const EdgeDistance INVALID_EDGE_DISTANCE = std::numeric_limits::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 diff --git a/profiles/testbot.lua b/profiles/testbot.lua index d4288bd8137..69f84c27f97 100644 --- a/profiles/testbot.lua +++ b/profiles/testbot.lua @@ -32,7 +32,7 @@ function setup() primary = 36, secondary = 18, tertiary = 12, - steps = 6, + steps = 6 } } end diff --git a/src/engine/plugins/table.cpp b/src/engine/plugins/table.cpp index 54804788eb4..d0c82091a4a 100644 --- a/src/engine/plugins/table.cpp +++ b/src/engine/plugins/table.cpp @@ -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; } diff --git a/src/engine/plugins/tile.cpp b/src/engine/plugins/tile.cpp index 46a1493dd4b..c01aafecfb5 100644 --- a/src/engine/plugins/tile.cpp +++ b/src/engine/plugins/tile.cpp @@ -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( diff --git a/src/engine/plugins/trip.cpp b/src/engine/plugins/trip.cpp index 2347677ff43..8914c3e4834 100644 --- a/src/engine/plugins/trip.cpp +++ b/src/engine/plugins/trip.cpp @@ -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( - algorithms.ManyToManySearch(snapped_phantoms, {}, {}), number_of_locations); - - if (result_table.size() == 0) + auto result_duration_table = util::DistTableWrapper( + algorithms + .ManyToManySearch( + snapped_phantoms, {}, {}, /*requestDistance*/ false, /*requestDuration*/ true) + .first, + number_of_locations); + + 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 trip; - trip.reserve(number_of_locations); + std::vector 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> trips = {trip}; + const std::vector> trips = {duration_trip}; const std::vector routes = {route}; api::TripAPI trip_api{facade, parameters}; trip_api.MakeResponse(trips, routes, snapped_phantoms, json_result); diff --git a/src/engine/routing_algorithms/direct_shortest_path.cpp b/src/engine/routing_algorithms/direct_shortest_path.cpp index 182bb7a9e9f..08ca3249e24 100644 --- a/src/engine/routing_algorithms/direct_shortest_path.cpp +++ b/src/engine/routing_algorithms/direct_shortest_path.cpp @@ -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" diff --git a/src/engine/routing_algorithms/many_to_many_ch.cpp b/src/engine/routing_algorithms/many_to_many_ch.cpp index 807406281f8..ebe69ea268a 100644 --- a/src/engine/routing_algorithms/many_to_many_ch.cpp +++ b/src/engine/routing_algorithms/many_to_many_ch.cpp @@ -60,8 +60,8 @@ void relaxOutgoingEdges(const DataFacade &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 &facade, } void forwardRoutingStep(const DataFacade &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::ManyToManyQueryHeap &query_heap, const std::vector &search_space_with_buckets, std::vector &weights_table, std::vector &durations_table, + std::vector &middle_nodes_table, const PhantomNode &phantom_node) { const auto node = query_heap.DeleteMin(); @@ -105,12 +106,12 @@ void forwardRoutingStep(const DataFacade &facade, for (const auto ¤t_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 ¤t_weight = weights_table[row_idx * number_of_targets + column_idx]; - auto ¤t_duration = durations_table[row_idx * number_of_targets + column_idx]; + auto ¤t_weight = weights_table[row_index * number_of_targets + column_index]; + auto ¤t_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 &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 &facade, } void backwardRoutingStep(const DataFacade &facade, - const unsigned column_idx, + const unsigned column_index, typename SearchEngineData::ManyToManyQueryHeap &query_heap, std::vector &search_space_with_buckets, const PhantomNode &phantom_node) @@ -144,9 +147,11 @@ void backwardRoutingStep(const DataFacade &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( facade, node, target_weight, target_duration, query_heap, phantom_node); @@ -154,26 +159,187 @@ void backwardRoutingStep(const DataFacade &facade, } // namespace ch +void retrievePackedPathFromSearchSpace(const NodeID middle_node_id, + const unsigned column_index, + const std::vector &search_space_with_buckets, + std::vector &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::ManyToManyQueryHeap &query_heap, + const DataFacade &facade, + const std::vector &phantom_nodes, + const std::vector &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 &search_space_with_buckets, + std::vector &distances_table, + const std::vector &middle_nodes_table) +{ + std::vector packed_leg; + + for (auto column_index : util::irange(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(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::vector manyToManySearch(SearchEngineData &engine_working_data, - const DataFacade &facade, - const std::vector &phantom_nodes, - const std::vector &source_indices, - const std::vector &target_indices) +std::pair, std::vector> +manyToManySearch(SearchEngineData &engine_working_data, + const DataFacade &facade, + const std::vector &phantom_nodes, + const std::vector &source_indices, + const std::vector &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 weights_table(number_of_entries, INVALID_EDGE_WEIGHT); std::vector durations_table(number_of_entries, MAXIMAL_EDGE_DURATION); + std::vector distances_table; + std::vector middle_nodes_table(number_of_entries, SPECIAL_NODEID); std::vector 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 manyToManySearch(SearchEngineData &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 manyToManySearch(SearchEngineData &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 diff --git a/src/engine/routing_algorithms/many_to_many_mld.cpp b/src/engine/routing_algorithms/many_to_many_mld.cpp index df9010bfbef..51c055bce47 100644 --- a/src/engine/routing_algorithms/many_to_many_mld.cpp +++ b/src/engine/routing_algorithms/many_to_many_mld.cpp @@ -207,14 +207,16 @@ void relaxOutgoingEdges(const DataFacade &facade, // Unidirectional multi-layer Dijkstra search for 1-to-N and N-to-1 matrices // template -std::vector oneToManySearch(SearchEngineData &engine_working_data, - const DataFacade &facade, - const std::vector &phantom_nodes, - std::size_t phantom_index, - const std::vector &phantom_indices) +std::pair, std::vector> +oneToManySearch(SearchEngineData &engine_working_data, + const DataFacade &facade, + const std::vector &phantom_nodes, + std::size_t phantom_index, + const std::vector &phantom_indices) { std::vector weights(phantom_indices.size(), INVALID_EDGE_WEIGHT); std::vector durations(phantom_indices.size(), MAXIMAL_EDGE_DURATION); + std::vector distances(phantom_indices.size(), INVALID_EDGE_DISTANCE); // Collect destination (source) nodes into a map std::unordered_multimap> @@ -364,7 +366,7 @@ std::vector oneToManySearch(SearchEngineData &engine_wo phantom_indices); } - return durations; + return std::make_pair(durations, distances); } // @@ -432,9 +434,11 @@ void backwardRoutingStep(const DataFacade &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 &facade, } template -std::vector manyToManySearch(SearchEngineData &engine_working_data, - const DataFacade &facade, - const std::vector &phantom_nodes, - const std::vector &source_indices, - const std::vector &target_indices) +std::pair, std::vector> +manyToManySearch(SearchEngineData &engine_working_data, + const DataFacade &facade, + const std::vector &phantom_nodes, + const std::vector &source_indices, + const std::vector &target_indices) { const auto number_of_sources = source_indices.size(); const auto number_of_targets = target_indices.size(); @@ -456,6 +461,7 @@ std::vector manyToManySearch(SearchEngineData &engine_w std::vector weights_table(number_of_entries, INVALID_EDGE_WEIGHT); std::vector durations_table(number_of_entries, MAXIMAL_EDGE_DURATION); + std::vector distances_table(number_of_entries, MAXIMAL_EDGE_DURATION); std::vector search_space_with_buckets; @@ -516,7 +522,7 @@ std::vector manyToManySearch(SearchEngineData &engine_w } } - return durations_table; + return std::make_pair(durations_table, distances_table); } } // namespace mld @@ -534,12 +540,20 @@ std::vector manyToManySearch(SearchEngineData &engine_w // then search is performed on a reversed graph with phantom nodes with flipped roles and // returning a transposed matrix. template <> -std::vector manyToManySearch(SearchEngineData &engine_working_data, - const DataFacade &facade, - const std::vector &phantom_nodes, - const std::vector &source_indices, - const std::vector &target_indices) +std::pair, std::vector> +manyToManySearch(SearchEngineData &engine_working_data, + const DataFacade &facade, + const std::vector &phantom_nodes, + const std::vector &source_indices, + const std::vector &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( diff --git a/src/engine/routing_algorithms/routing_base_ch.cpp b/src/engine/routing_algorithms/routing_base_ch.cpp index 4bdd0a73c18..695ebe9234a 100644 --- a/src/engine/routing_algorithms/routing_base_ch.cpp +++ b/src/engine/routing_algorithms/routing_base_ch.cpp @@ -59,6 +59,24 @@ void retrievePackedPathFromSingleHeap(const SearchEngineData::QueryHe } } +void retrievePackedPathFromSingleManyToManyHeap( + const SearchEngineData::ManyToManyQueryHeap &search_heap, + const NodeID middle_node_id, + std::vector &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. diff --git a/src/engine/routing_algorithms/tile_turns.cpp b/src/engine/routing_algorithms/tile_turns.cpp index 17ea540e3df..6fb419c6b6d 100644 --- a/src/engine/routing_algorithms/tile_turns.cpp +++ b/src/engine/routing_algorithms/tile_turns.cpp @@ -101,7 +101,6 @@ std::vector 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) { diff --git a/src/nodejs/node_osrm.cpp b/src/nodejs/node_osrm.cpp index e98e251d28a..23fcb30252f 100644 --- a/src/nodejs/node_osrm.cpp +++ b/src/nodejs/node_osrm.cpp @@ -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 * }); diff --git a/test/nodejs/table.js b/test/nodejs/table.js index 972550614d4..f053001ec02 100644 --- a/test/nodejs/table.js +++ b/test/nodejs/table.js @@ -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); + 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: ' + 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: 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: ' + 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: 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: ' + 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: 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); + 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); + }); }); }); diff --git a/unit_tests/library/table.cpp b/unit_tests/library/table.cpp index 45ae643cdb4..f0b7528ae2d 100644 --- a/unit_tests/library/table.cpp +++ b/unit_tests/library/table.cpp @@ -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().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().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().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; diff --git a/unit_tests/mocks/mock_datafacade.hpp b/unit_tests/mocks/mock_datafacade.hpp index 75a864fd85e..2342d47527c 100644 --- a/unit_tests/mocks/mock_datafacade.hpp +++ b/unit_tests/mocks/mock_datafacade.hpp @@ -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}; diff --git a/unit_tests/server/parameters_parser.cpp b/unit_tests/server/parameters_parser.cpp index 4e451cefa42..4d3b8c53b59 100644 --- a/unit_tests/server/parameters_parser.cpp +++ b/unit_tests/server/parameters_parser.cpp @@ -86,11 +86,13 @@ BOOST_AUTO_TEST_CASE(invalid_table_urls) testInvalidOptions("1,2;3,4?sources=1&destinations=1&bla=foo"), 32UL); BOOST_CHECK_EQUAL(testInvalidOptions("1,2;3,4?sources=foo"), 16UL); BOOST_CHECK_EQUAL(testInvalidOptions("1,2;3,4?destinations=foo"), 21UL); + BOOST_CHECK_EQUAL( + testInvalidOptions("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(result_2->annotations_type & (RouteParameters::AnnotationsType::Weight | - RouteParameters::AnnotationsType::Duration | - RouteParameters::AnnotationsType::Nodes)), - true); + BOOST_CHECK_EQUAL(static_cast(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( + "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( + "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( + "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("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) diff --git a/unit_tests/util/packed_vector.cpp b/unit_tests/util/packed_vector.cpp index 56a66f1b23b..e8d55d9c228 100644 --- a/unit_tests/util/packed_vector.cpp +++ b/unit_tests/util/packed_vector.cpp @@ -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); }