From fa525ad610af14c03edcd366571c51c100f61d08 Mon Sep 17 00:00:00 2001 From: Daniel Patterson Date: Sun, 8 May 2016 22:58:13 -0700 Subject: [PATCH] Return an array with meta-data for each coordinate. Currently supports duration and distance for each coordinate. This is particularly useful in map-matching, comparing how a trip progresses compared to a real GPS trace that is map-matched. --- CHANGELOG.md | 7 +++++ docs/http.md | 10 +++++++ features/step_definitions/matching.js | 14 +++++++++- features/support/route.js | 10 +++++++ features/testbot/matching.feature | 15 +++++++++++ include/engine/api/match_parameters.hpp | 1 + include/engine/api/route_api.hpp | 26 ++++++++++++++++++- include/engine/api/route_parameters.hpp | 5 +++- include/engine/guidance/assemble_geometry.hpp | 16 ++++++++---- include/engine/guidance/leg_geometry.hpp | 7 +++++ .../server/api/route_parameters_grammar.hpp | 1 + src/engine/guidance/post_processing.cpp | 3 +++ unit_tests/server/parameters_parser.cpp | 16 ++++++++++-- 13 files changed, 121 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54ad91514..f5fe75f1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 5.2 + Changes from 5.1 + + - API: + - new parameter `annotate` for `route` and `match` requests. Returns additional data about each + coordinate along the selected/matched route line. + # 5.1.0 Changes with regard to 5.0.0 diff --git a/docs/http.md b/docs/http.md index cbf0fe880..24da1d1ad 100644 --- a/docs/http.md +++ b/docs/http.md @@ -145,6 +145,7 @@ In addition to the [general options](#general-options) the following options are |------------|------------------------------------------|-------------------------------------------------------------------------------| |alternatives|`true`, `false` (default) |Search for alternative routes and return as well.\* | |steps |`true`, `false` (default) |Return route steps for each route leg | +|annotate |`true`, `false` (default) |Returns additional metadata for each coordinate along the route geometry. | |geometries |`polyline` (default), `geojson` |Returned route geometry format (influences overview and per step) | |overview |`simplified` (default), `full`, `false` |Add overview geometry either full, simplified according to highest zoom level it could be display on, or not at all.| |continue_straight |`default` (default), `true`, `false`|Forces the route to keep going straight at waypoints and don't do a uturn even if it would be faster. Default value depends on the profile. | @@ -247,6 +248,7 @@ In addition to the [general options](#general-options) the following options are |------------|------------------------------------------------|------------------------------------------------------------------------------------------| |steps |`true`, `false` (default) |Return route steps for each route | |geometries |`polyline` (default), `geojson` |Returned route geometry format (influences overview and per step) | +|annotate |`true`, `false` (default) |Returns additional metadata for each coordinate along the route geometry. | |overview |`simplified` (default), `full`, `false` |Add overview geometry either full, simplified according to highest zoom level it could be display on, or not at all.| |timestamps |`{timestamp};{timestamp}[;{timestamp} ...]` |Timestamp of the input location. | |radiuses |`{radius};{radius}[;{radius} ...]` |Standard deviation of GPS precision used for map matching. If applicable use GPS accuracy.| @@ -292,6 +294,7 @@ In addition to the [general options](#general-options) the following options are |Option |Values |Description | |------------|------------------------------------------------|---------------------------------------------------------------------------| |steps |`true`, `false` (default) |Return route instructions for each trip | +|annotate |`true`, `false` (default) |Returns additional metadata for each coordinate along the route geometry. | |geometries |`polyline` (default), `geojson` |Returned route geometry format (influences overview and per step) | |overview |`simplified` (default), `full`, `false` |Add overview geometry either full, simplified according to highest zoom level it could be display on, or not at all.| @@ -377,6 +380,13 @@ Represents a route between two waypoints. | true | array of `RouteStep` objects describing the turn-by-turn instructions | | false | empty array | +- `annotation`: Additional details about each coordinate along the route geometry: + + | annotate | | + |--------------|-----------------------------------------------------------------------| + | true | returns distance and durations of each coordinate along the route | + | false | will not exist | + #### Example With `steps=false`: diff --git a/features/step_definitions/matching.js b/features/step_definitions/matching.js index 1b770d76e..de0a65c5d 100644 --- a/features/step_definitions/matching.js +++ b/features/step_definitions/matching.js @@ -33,7 +33,9 @@ module.exports = function () { var subMatchings = [], turns = '', route = '', - duration = ''; + duration = '', + annotation = ''; + if (res.statusCode === 200) { if (headers.has('matchings')) { @@ -54,6 +56,11 @@ module.exports = function () { if (json.matchings.length != 1) throw new Error('*** Checking duration only supported for matchings with one subtrace'); duration = json.matchings[0].duration; } + + if (headers.has('annotation')) { + if (json.matchings.length != 1) throw new Error('*** Checking annotation only supported for matchings with one subtrace'); + annotation = this.annotationList(json.matchings[0]); + } } if (headers.has('turns')) { @@ -68,6 +75,10 @@ module.exports = function () { got.duration = duration.toString(); } + if (headers.has('annotation')) { + got.annotation = annotation.toString(); + } + var ok = true; var encodedResult = '', extendedTarget = ''; @@ -134,6 +145,7 @@ module.exports = function () { this.requestUrl(row.request, afterRequest); } else { var params = this.queryParams; + params['annotate'] = 'true'; got = {}; for (var k in row) { var match = k.match(/param:(.*)/); diff --git a/features/support/route.js b/features/support/route.js index 99a683561..3c7f7a05c 100644 --- a/features/support/route.js +++ b/features/support/route.js @@ -141,6 +141,16 @@ module.exports = function () { return this.extractInstructionList(instructions, s => s.maneuver.bearing_after); }; + this.annotationList = (instructions) => { + // Pull out all the distinct segment distances, skipping the arrive + // instructions, and the leading 0 on all timestamps arrays. + var pairs = []; + for (var i in instructions.annotation.duration) { + pairs.push(instructions.annotation.duration[i]+":"+instructions.annotation.distance[i]); + } + return pairs.join(","); + }; + this.turnList = (instructions) => { return instructions.legs.reduce((m, v) => m.concat(v.steps), []) .map(v => { diff --git a/features/testbot/matching.feature b/features/testbot/matching.feature index 71ac0776c..a7991d49e 100644 --- a/features/testbot/matching.feature +++ b/features/testbot/matching.feature @@ -104,3 +104,18 @@ Feature: Basic Map Matching | trace | matchings | | dcba | hg,gf,fe | | efgh | ab,bc,cd | + + Scenario: Testbot - Duration details + Given the node map + | a | b | c | d | e | | g | h | + | | | i | | | | | | + + And the ways + | nodes | oneway | + | abcdegh | no | + | ci | no | + + When I match I should get + | trace | matchings | annotation | + | abeh | abcedgh | 1:9.897633,0:0,1:10.008842,1:10.008842,1:10.008842,0:0,2:20.017685,1:10.008842 | + | abci | abc,ci | 1:9.897633,0:0,1:10.008842,0:0.111209,1:10.121593 | diff --git a/include/engine/api/match_parameters.hpp b/include/engine/api/match_parameters.hpp index 0cb4741ea..87e67ebbb 100644 --- a/include/engine/api/match_parameters.hpp +++ b/include/engine/api/match_parameters.hpp @@ -52,6 +52,7 @@ struct MatchParameters : public RouteParameters { MatchParameters() : RouteParameters(false, + false, false, RouteParameters::GeometriesType::Polyline, RouteParameters::OverviewType::Simplified, diff --git a/include/engine/api/route_api.hpp b/include/engine/api/route_api.hpp index b8cbee215..52d702ea3 100644 --- a/include/engine/api/route_api.hpp +++ b/include/engine/api/route_api.hpp @@ -176,9 +176,33 @@ class RouteAPI : public BaseAPI }); } - return json::makeRoute(route, + auto result = json::makeRoute(route, json::makeRouteLegs(std::move(legs), std::move(step_geometries)), std::move(json_overview)); + + if (parameters.annotation) + { + util::json::Array durations; + util::json::Array distances; + for (const auto idx : util::irange(0UL, leg_geometries.size())) + { + auto &leg_geometry = leg_geometries[idx]; + std::for_each(leg_geometry.annotations.begin(), + leg_geometry.annotations.end(), + [this, &durations, &distances](const guidance::LegGeometry::Annotation &step) { + durations.values.push_back(step.duration); + distances.values.push_back(step.distance); + }); + } + + util::json::Object details; + details.values["distance"] = std::move(distances); + details.values["duration"] = std::move(durations); + + result.values["annotation"] = std::move(details); + } + + return result; } const RouteParameters ¶meters; diff --git a/include/engine/api/route_parameters.hpp b/include/engine/api/route_parameters.hpp index 59040205a..3bb9fe9d6 100644 --- a/include/engine/api/route_parameters.hpp +++ b/include/engine/api/route_parameters.hpp @@ -72,17 +72,20 @@ struct RouteParameters : public BaseParameters template RouteParameters(const bool steps_, const bool alternatives_, + const bool annotation_, const GeometriesType geometries_, const OverviewType overview_, const boost::optional continue_straight_, Args... args_) : BaseParameters{std::forward(args_)...}, steps{steps_}, alternatives{alternatives_}, - geometries{geometries_}, overview{overview_}, continue_straight{continue_straight_} + annotation{annotation_}, geometries{geometries_}, overview{overview_}, + continue_straight{continue_straight_} { } bool steps = false; bool alternatives = false; + bool annotation = false; GeometriesType geometries = GeometriesType::Polyline; OverviewType overview = OverviewType::Simplified; boost::optional continue_straight; diff --git a/include/engine/guidance/assemble_geometry.hpp b/include/engine/guidance/assemble_geometry.hpp index 4fd84cbdd..ffb195e8b 100644 --- a/include/engine/guidance/assemble_geometry.hpp +++ b/include/engine/guidance/assemble_geometry.hpp @@ -43,34 +43,40 @@ LegGeometry assembleGeometry(const DataFacadeT &facade, geometry.segment_offsets.push_back(0); geometry.locations.push_back(source_node.location); + auto cumulative_distance = 0.; auto current_distance = 0.; auto prev_coordinate = geometry.locations.front(); for (const auto &path_point : leg_data) { auto coordinate = facade.GetCoordinateOfNode(path_point.turn_via_node); - current_distance += + current_distance = util::coordinate_calculation::haversineDistance(prev_coordinate, coordinate); + cumulative_distance += current_distance; // all changes to this check have to be matched with assemble_steps if (path_point.turn_instruction.type != extractor::guidance::TurnType::NoTurn) { - geometry.segment_distances.push_back(current_distance); + geometry.segment_distances.push_back(cumulative_distance); geometry.segment_offsets.push_back(geometry.locations.size()); - current_distance = 0.; + cumulative_distance = 0.; } prev_coordinate = coordinate; + geometry.annotations.emplace_back(LegGeometry::Annotation{current_distance, path_point.duration_until_turn / 10.}); geometry.locations.push_back(std::move(coordinate)); } - current_distance += + current_distance = util::coordinate_calculation::haversineDistance(prev_coordinate, target_node.location); + cumulative_distance += current_distance; // segment leading to the target node - geometry.segment_distances.push_back(current_distance); + geometry.segment_distances.push_back(cumulative_distance); + geometry.annotations.emplace_back(LegGeometry::Annotation{current_distance, target_node.forward_weight / 10.}); geometry.segment_offsets.push_back(geometry.locations.size()); geometry.locations.push_back(target_node.location); BOOST_ASSERT(geometry.segment_distances.size() == geometry.segment_offsets.size() - 1); BOOST_ASSERT(geometry.locations.size() > geometry.segment_distances.size()); + BOOST_ASSERT(geometry.annotations.size() == geometry.locations.size() - 1); return geometry; } diff --git a/include/engine/guidance/leg_geometry.hpp b/include/engine/guidance/leg_geometry.hpp index 622e40f52..7de585224 100644 --- a/include/engine/guidance/leg_geometry.hpp +++ b/include/engine/guidance/leg_geometry.hpp @@ -31,6 +31,13 @@ struct LegGeometry // length of the segment in meters std::vector segment_distances; + // Per-coordinate metadata + struct Annotation { + double distance; + double duration; + }; + std::vector annotations; + std::size_t FrontIndex(std::size_t segment_index) const { return segment_offsets[segment_index]; diff --git a/include/server/api/route_parameters_grammar.hpp b/include/server/api/route_parameters_grammar.hpp index 46d10d1b2..96dad39cb 100644 --- a/include/server/api/route_parameters_grammar.hpp +++ b/include/server/api/route_parameters_grammar.hpp @@ -57,6 +57,7 @@ struct RouteParametersGrammar : public BaseParametersGrammarsteps); BOOST_CHECK_EQUAL(reference_2.alternatives, result_2->alternatives); BOOST_CHECK_EQUAL(reference_2.geometries, result_2->geometries); + BOOST_CHECK_EQUAL(reference_2.annotation, result_2->annotation); BOOST_CHECK_EQUAL(reference_2.overview, result_2->overview); BOOST_CHECK_EQUAL(reference_2.continue_straight, result_2->continue_straight); CHECK_EQUAL_RANGE(reference_2.bearings, result_2->bearings); @@ -109,7 +112,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) CHECK_EQUAL_RANGE(reference_2.coordinates, result_2->coordinates); CHECK_EQUAL_RANGE(reference_2.hints, result_2->hints); - RouteParameters reference_3{false, false, RouteParameters::GeometriesType::GeoJSON, + RouteParameters reference_3{false, false, false, RouteParameters::GeometriesType::GeoJSON, RouteParameters::OverviewType::False, true}; reference_3.coordinates = coords_1; auto result_3 = api::parseParameters( @@ -119,6 +122,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) BOOST_CHECK_EQUAL(reference_3.steps, result_3->steps); BOOST_CHECK_EQUAL(reference_3.alternatives, result_3->alternatives); BOOST_CHECK_EQUAL(reference_3.geometries, result_3->geometries); + BOOST_CHECK_EQUAL(reference_3.annotation, result_3->annotation); BOOST_CHECK_EQUAL(reference_3.overview, result_3->overview); BOOST_CHECK_EQUAL(reference_3.continue_straight, result_3->continue_straight); CHECK_EQUAL_RANGE(reference_3.bearings, result_3->bearings); @@ -137,6 +141,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) "39KAAAAHgAAACEAAAAAAAAAGAAAAE0BAABOAQAAGwAAAIAzcQBkUJsC1zNxAHBQmw" "IAAAEBl-Umfg==")}; RouteParameters reference_4{false, + false, false, RouteParameters::GeometriesType::Polyline, RouteParameters::OverviewType::Simplified, @@ -154,6 +159,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) BOOST_CHECK_EQUAL(reference_4.steps, result_4->steps); BOOST_CHECK_EQUAL(reference_4.alternatives, result_4->alternatives); BOOST_CHECK_EQUAL(reference_4.geometries, result_4->geometries); + BOOST_CHECK_EQUAL(reference_4.annotation, result_4->annotation); BOOST_CHECK_EQUAL(reference_4.overview, result_4->overview); BOOST_CHECK_EQUAL(reference_4.continue_straight, result_4->continue_straight); CHECK_EQUAL_RANGE(reference_4.bearings, result_4->bearings); @@ -165,6 +171,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) boost::none, engine::Bearing{200, 10}, engine::Bearing{100, 5}, }; RouteParameters reference_5{false, + false, false, RouteParameters::GeometriesType::Polyline, RouteParameters::OverviewType::Simplified, @@ -178,6 +185,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) BOOST_CHECK_EQUAL(reference_5.steps, result_5->steps); BOOST_CHECK_EQUAL(reference_5.alternatives, result_5->alternatives); BOOST_CHECK_EQUAL(reference_5.geometries, result_5->geometries); + BOOST_CHECK_EQUAL(reference_5.annotation, result_5->annotation); BOOST_CHECK_EQUAL(reference_5.overview, result_5->overview); BOOST_CHECK_EQUAL(reference_5.continue_straight, result_5->continue_straight); CHECK_EQUAL_RANGE(reference_5.bearings, result_5->bearings); @@ -196,6 +204,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) BOOST_CHECK_EQUAL(reference_6.steps, result_6->steps); BOOST_CHECK_EQUAL(reference_6.alternatives, result_6->alternatives); BOOST_CHECK_EQUAL(reference_6.geometries, result_6->geometries); + BOOST_CHECK_EQUAL(reference_6.annotation, result_6->annotation); BOOST_CHECK_EQUAL(reference_6.overview, result_6->overview); BOOST_CHECK_EQUAL(reference_6.continue_straight, result_6->continue_straight); CHECK_EQUAL_RANGE(reference_6.bearings, result_6->bearings); @@ -211,6 +220,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) BOOST_CHECK_EQUAL(reference_7.steps, result_7->steps); BOOST_CHECK_EQUAL(reference_7.alternatives, result_7->alternatives); BOOST_CHECK_EQUAL(reference_7.geometries, result_7->geometries); + BOOST_CHECK_EQUAL(reference_7.annotation, result_7->annotation); BOOST_CHECK_EQUAL(reference_7.overview, result_7->overview); BOOST_CHECK_EQUAL(reference_7.continue_straight, result_7->continue_straight); CHECK_EQUAL_RANGE(reference_7.bearings, result_7->bearings); @@ -247,6 +257,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) "IFAAEBl-Umfg=="), boost::none}; RouteParameters reference_10{false, + false, false, RouteParameters::GeometriesType::Polyline, RouteParameters::OverviewType::Simplified, @@ -263,6 +274,7 @@ BOOST_AUTO_TEST_CASE(valid_route_urls) BOOST_CHECK_EQUAL(reference_10.steps, result_10->steps); BOOST_CHECK_EQUAL(reference_10.alternatives, result_10->alternatives); BOOST_CHECK_EQUAL(reference_10.geometries, result_10->geometries); + BOOST_CHECK_EQUAL(reference_10.annotation, result_10->annotation); BOOST_CHECK_EQUAL(reference_10.overview, result_10->overview); BOOST_CHECK_EQUAL(reference_10.continue_straight, result_10->continue_straight); CHECK_EQUAL_RANGE(reference_10.bearings, result_10->bearings);