diff --git a/CHANGELOG.md b/CHANGELOG.md index 93379cc93..83eb57dd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ # 5.14.3 - Changes from 5.14.2: + - Features: + - Added a `waypoints` parameter to the match service plugin that accepts indices to input coordinates and treats only those points as waypoints in the response format. - Bugfixes: - FIXED #4754: U-Turn penalties are applied to straight turns. - FIXED #4756: Removed too restrictive road name check in the sliproad handler diff --git a/features/step_definitions/matching.js b/features/step_definitions/matching.js index ef66d7b3c..1b96e0a8a 100644 --- a/features/step_definitions/matching.js +++ b/features/step_definitions/matching.js @@ -150,7 +150,8 @@ module.exports = function () { } var ok = true; var encodedResult = '', - extendedTarget = ''; + extendedTarget = '', + resultWaypoints = []; var testSubMatching = (sub, si) => { var testSubNode = (ni) => { @@ -186,6 +187,29 @@ module.exports = function () { }); } + if (headers.has('waypoints')) { + var got_loc = []; + for (let i = 0; i < json.tracepoints.length; i++) { + if (!json.tracepoints[i]) continue; + if (json.tracepoints[i].waypoint_index != null) + got_loc.push(json.tracepoints[i].location); + } + + if (row.waypoints.length != got_loc.length) + return cb(new Error(`Expected ${row.waypoints.length} waypoints, got ${got_loc.length}`)); + + for (i = 0; i < row.waypoints.length; i++) + { + var want_node = this.findNodeByName(row.waypoints[i]); + if (!this.FuzzyMatch.matchLocation(got_loc[i], want_node)) { + resultWaypoints.push(util.format('? [%s,%s]', got_loc[i][0], got_loc[i][1])); + ok = false; + } else { + resultWaypoints.push(row.waypoints[i]); + } + } + } + if (ok) { if (headers.has('matchings')) { got.matchings = row.matchings; @@ -194,7 +218,12 @@ module.exports = function () { if (headers.has('timestamps')) { got.timestamps = row.timestamps; } + + if (headers.has('waypoints')) { + got.waypoints = row.waypoints; + } } else { + got.waypoints = resultWaypoints.join(';'); got.matchings = encodedResult; row.matchings = extendedTarget; } diff --git a/features/testbot/matching.feature b/features/testbot/matching.feature index c5ae5cea5..77eea3545 100644 --- a/features/testbot/matching.feature +++ b/features/testbot/matching.feature @@ -480,3 +480,126 @@ Feature: Basic Map Matching | trace | a:nodes | | 12 | 1:2:3:4:5:6 | | 21 | 6:5:4:3:2:1 | + + + Scenario: Matching with waypoints param for start/end + Given the node map + """ + a-----b---c + | + | + d + | + | + e + """ + And the ways + | nodes | oneway | + | abc | no | + | bde | no | + + Given the query options + | waypoints | 0;3 | + + When I match I should get + | trace | code | matchings | waypoints | + | abde | Ok | abde | ae | + + Scenario: Matching with waypoints param that were tidied away + Given the node map + """ + a - b - c - e + | + f + | + g + """ + And the ways + | nodes | oneway | + | abce | no | + | cfg | no | + + Given the query options + | tidy | true | + | waypoints | 0;2;5 | + + When I match I should get + | trace | code | matchings | waypoints | + | abccfg | Ok | abcfg | acg | + + Scenario: Testbot - Map matching refuses to use waypoints with trace splitting + Given the node map + """ + a b c d + e + """ + Given the query options + | waypoints | 0;3 | + + And the ways + | nodes | oneway | + | abcd | no | + + When I match I should get + | trace | timestamps | code | + | abcd | 0 1 62 63 | NoMatch | + + Scenario: Testbot - Map matching invalid waypoints + Given the node map + """ + a b c d + e + """ + Given the query options + | waypoints | 0;4 | + + And the ways + | nodes | oneway | + | abcd | no | + + When I match I should get + | trace | code | + | abcd | InvalidOptions | + + Scenario: Matching fail with waypoints param missing start/end + Given the node map + """ + a-----b---c + | + | + d + | + | + e + """ + And the ways + | nodes | oneway | + | abc | no | + | bde | no | + + Given the query options + | waypoints | 1;3 | + + When I match I should get + | trace | code | + | abde | InvalidValue | + + Scenario: Testbot - Map matching with outlier that has no candidate and waypoint parameter + Given a grid size of 100 meters + Given the node map + """ + a b c d + + 1 + """ + + And the ways + | nodes | oneway | + | abcd | no | + + Given the query options + | waypoints | 0;2;3 | + + When I match I should get + | trace | timestamps | code | + | ab1d | 0 1 2 3 | NoMatch | diff --git a/include/engine/api/match_api.hpp b/include/engine/api/match_api.hpp index c06297941..bc3c29cda 100644 --- a/include/engine/api/match_api.hpp +++ b/include/engine/api/match_api.hpp @@ -86,6 +86,10 @@ class MatchAPI final : public RouteAPI for (auto point_index : util::irange( 0u, static_cast(sub_matchings[sub_matching_index].indices.size()))) { + // tidied_to_original: index of the input coordinate that a tidied coordinate + // corresponds to + // sub_matching indices: index of the coordinate passed to map matching plugin that + // a matched node corresponds to trace_idx_to_matching_idx[tidy_result .tidied_to_original[sub_matchings[sub_matching_index] .indices[point_index]]] = @@ -93,6 +97,7 @@ class MatchAPI final : public RouteAPI } } + std::size_t was_waypoint_idx = 0; for (auto trace_index : util::irange(0UL, parameters.coordinates.size())) { if (tidy_result.can_be_removed[trace_index]) @@ -110,10 +115,18 @@ class MatchAPI final : public RouteAPI sub_matchings[matching_index.sub_matching_index].nodes[matching_index.point_index]; auto waypoint = BaseAPI::MakeWaypoint(phantom); waypoint.values["matchings_index"] = matching_index.sub_matching_index; - waypoint.values["waypoint_index"] = matching_index.point_index; waypoint.values["alternatives_count"] = sub_matchings[matching_index.sub_matching_index] .alternatives_count[matching_index.point_index]; + if (tidy_result.was_waypoint[trace_index]) + { + waypoint.values["waypoint_index"] = was_waypoint_idx; + was_waypoint_idx++; + } + else + { + waypoint.values["waypoint_index"] = util::json::Null(); + } waypoints.values.push_back(std::move(waypoint)); } diff --git a/include/engine/api/match_parameters.hpp b/include/engine/api/match_parameters.hpp index a72f393eb..79ce84c55 100644 --- a/include/engine/api/match_parameters.hpp +++ b/include/engine/api/match_parameters.hpp @@ -63,25 +63,40 @@ struct MatchParameters : public RouteParameters RouteParameters::GeometriesType::Polyline, RouteParameters::OverviewType::Simplified, {}), - gaps(GapsType::Split), tidy(false) + gaps(GapsType::Split), tidy(false), waypoints() { } template MatchParameters(std::vector timestamps_, GapsType gaps_, bool tidy_, Args... args_) + : MatchParameters(std::move(timestamps_), gaps_, tidy_, {}, std::forward(args_)...) + { + } + + template + MatchParameters(std::vector timestamps_, + GapsType gaps_, + bool tidy_, + std::vector waypoints_, + Args... args_) : RouteParameters{std::forward(args_)...}, timestamps{std::move(timestamps_)}, - gaps(gaps_), tidy(tidy_) + gaps(gaps_), tidy(tidy_), waypoints{std::move(waypoints_)} { } std::vector timestamps; GapsType gaps; bool tidy; + std::vector waypoints; bool IsValid() const { + const auto valid_waypoints = + std::all_of(waypoints.begin(), waypoints.end(), [this](const auto &w) { + return w < coordinates.size(); + }); return RouteParameters::IsValid() && - (timestamps.empty() || timestamps.size() == coordinates.size()); + (timestamps.empty() || timestamps.size() == coordinates.size()) && valid_waypoints; } }; } diff --git a/include/engine/api/match_parameters_tidy.hpp b/include/engine/api/match_parameters_tidy.hpp index b4e3a2fea..7a54b0605 100644 --- a/include/engine/api/match_parameters_tidy.hpp +++ b/include/engine/api/match_parameters_tidy.hpp @@ -37,6 +37,9 @@ struct Result Mask can_be_removed; // Maps the MatchParameter's original items to items which should not be removed. Mapping tidied_to_original; + // Masking the MatchParameter coordinates for items whose indices were present in the + // `waypoints` parameter. + Mask was_waypoint; }; inline Result keep_all(const MatchParameters ¶ms) @@ -44,6 +47,15 @@ inline Result keep_all(const MatchParameters ¶ms) Result result; result.can_be_removed.resize(params.coordinates.size(), false); + result.was_waypoint.resize(params.coordinates.size(), true); + if (!params.waypoints.empty()) + { + for (const auto p : params.waypoints) + { + result.was_waypoint.set(p, false); + } + result.was_waypoint.flip(); + } result.tidied_to_original.reserve(params.coordinates.size()); for (std::size_t current = 0; current < params.coordinates.size(); ++current) { @@ -61,6 +73,8 @@ inline Result keep_all(const MatchParameters ¶ms) { result.parameters.coordinates.push_back(params.coordinates[i]); + if (result.was_waypoint[i]) + result.parameters.waypoints.push_back(result.parameters.coordinates.size() - 1); if (!params.hints.empty()) result.parameters.hints.push_back(params.hints[i]); @@ -74,6 +88,8 @@ inline Result keep_all(const MatchParameters ¶ms) result.parameters.timestamps.push_back(params.timestamps[i]); } } + if (params.waypoints.empty()) + result.parameters.waypoints.clear(); return result; } @@ -85,6 +101,15 @@ inline Result tidy(const MatchParameters ¶ms, Thresholds cfg = {15., 5}) Result result; result.can_be_removed.resize(params.coordinates.size(), false); + result.was_waypoint.resize(params.coordinates.size(), true); + if (!params.waypoints.empty()) + { + for (const auto p : params.waypoints) + { + result.was_waypoint.set(p, false); + } + result.was_waypoint.flip(); + } result.tidied_to_original.push_back(0); @@ -138,13 +163,14 @@ inline Result tidy(const MatchParameters ¶ms, Thresholds cfg = {15., 5}) // We have to filter parallel arrays that may be empty or the exact same size. // result.parameters contains an empty MatchParameters at this point: conditionally fill. - for (std::size_t i = 0; i < result.can_be_removed.size(); ++i) { if (!result.can_be_removed[i]) { result.parameters.coordinates.push_back(params.coordinates[i]); + if (result.was_waypoint[i]) + result.parameters.waypoints.push_back(result.parameters.coordinates.size() - 1); if (!params.hints.empty()) result.parameters.hints.push_back(params.hints[i]); @@ -157,8 +183,17 @@ inline Result tidy(const MatchParameters ¶ms, Thresholds cfg = {15., 5}) if (!params.timestamps.empty()) result.parameters.timestamps.push_back(params.timestamps[i]); } + else + { + // one of the coordinates meant to be used as a waypoint was marked for removal + // update the original waypoint index to the new representative coordinate + const auto last_idx = result.parameters.coordinates.size() - 1; + if (result.was_waypoint[i] && (result.parameters.waypoints.back() != last_idx)) + { + result.parameters.waypoints.push_back(last_idx); + } + } } - BOOST_ASSERT(result.tidied_to_original.size() == result.parameters.coordinates.size()); return result; } diff --git a/include/engine/internal_route_result.hpp b/include/engine/internal_route_result.hpp index bcd4303dc..87c673bc3 100644 --- a/include/engine/internal_route_result.hpp +++ b/include/engine/internal_route_result.hpp @@ -7,11 +7,11 @@ #include "engine/phantom_node.hpp" -#include "osrm/coordinate.hpp" - +#include "util/coordinate.hpp" #include "util/guidance/entry_class.hpp" #include "util/guidance/turn_bearing.hpp" #include "util/guidance/turn_lanes.hpp" +#include "util/integer_range.hpp" #include "util/typedefs.hpp" #include @@ -102,6 +102,56 @@ struct InternalManyRoutesResult std::vector routes; }; + +inline InternalRouteResult CollapseInternalRouteResult(const InternalRouteResult &leggy_result, + const std::vector &is_waypoint) +{ + BOOST_ASSERT(leggy_result.is_valid()); + BOOST_ASSERT(is_waypoint[0]); // first and last coords + BOOST_ASSERT(is_waypoint.back()); // should always be waypoints + // Nothing to collapse! return result as is + if (leggy_result.unpacked_path_segments.size() == 1) + return leggy_result; + + BOOST_ASSERT(leggy_result.segment_end_coordinates.size() > 1); + + InternalRouteResult collapsed; + collapsed.shortest_path_weight = leggy_result.shortest_path_weight; + for (auto i : util::irange(0, leggy_result.unpacked_path_segments.size())) + { + if (is_waypoint[i]) + { + // start another leg vector + collapsed.unpacked_path_segments.push_back(leggy_result.unpacked_path_segments[i]); + // save new phantom node pair + collapsed.segment_end_coordinates.push_back(leggy_result.segment_end_coordinates[i]); + // save data about phantom nodes + collapsed.source_traversed_in_reverse.push_back( + leggy_result.source_traversed_in_reverse[i]); + collapsed.target_traversed_in_reverse.push_back( + leggy_result.target_traversed_in_reverse[i]); + } + else + // no new leg, collapse the next segment into the last leg + { + BOOST_ASSERT(!collapsed.unpacked_path_segments.empty()); + auto &last_segment = collapsed.unpacked_path_segments.back(); + // deduplicate last segment (needs to be checked for empty for the same node query edge + // case) + if (!last_segment.empty()) + last_segment.pop_back(); + // update target phantom node of leg + BOOST_ASSERT(!collapsed.segment_end_coordinates.empty()); + collapsed.segment_end_coordinates.back().target_phantom = + leggy_result.segment_end_coordinates[i].target_phantom; + // copy path segments into current leg + last_segment.insert(last_segment.end(), + leggy_result.unpacked_path_segments[i].begin(), + leggy_result.unpacked_path_segments[i].end()); + } + } + return collapsed; +} } } diff --git a/include/server/api/match_parameter_grammar.hpp b/include/server/api/match_parameter_grammar.hpp index 1ea190a11..bb9b6f943 100644 --- a/include/server/api/match_parameter_grammar.hpp +++ b/include/server/api/match_parameter_grammar.hpp @@ -28,17 +28,31 @@ struct MatchParametersGrammar final : public RouteParametersGrammar::value) + size_t_ = qi::ulong_long; + else + size_t_ = qi::ulong_; +#else + size_t_ = qi::ulong_; +#endif + timestamps_rule = qi::lit("timestamps=") > (qi::uint_ % ';')[ph::bind(&engine::api::MatchParameters::timestamps, qi::_r1) = qi::_1]; + waypoints_rule = + qi::lit("waypoints=") > + (size_t_ % ';')[ph::bind(&engine::api::MatchParameters::waypoints, qi::_r1) = qi::_1]; + gaps_type.add("split", engine::api::MatchParameters::GapsType::Split)( "ignore", engine::api::MatchParameters::GapsType::Ignore); root_rule = BaseGrammar::query_rule(qi::_r1) > -qi::lit(".json") > -('?' > (timestamps_rule(qi::_r1) | BaseGrammar::base_rule(qi::_r1) | + waypoints_rule(qi::_r1) | (qi::lit("gaps=") > gaps_type[ph::bind(&engine::api::MatchParameters::gaps, qi::_r1) = qi::_1]) | (qi::lit("tidy=") > @@ -49,6 +63,8 @@ struct MatchParametersGrammar final : public RouteParametersGrammar root_rule; qi::rule timestamps_rule; + qi::rule waypoints_rule; + qi::rule size_t_; qi::symbols gaps_type; }; diff --git a/src/engine/plugins/match.cpp b/src/engine/plugins/match.cpp index 961b1864f..c35f43eff 100644 --- a/src/engine/plugins/match.cpp +++ b/src/engine/plugins/match.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -174,6 +175,16 @@ Status MatchPlugin::HandleRequest(const RoutingAlgorithmsInterface &algorithms, tidied = api::tidy::keep_all(parameters); } + // Error: first and last points should be waypoints + if (!parameters.waypoints.empty() && + (tidied.parameters.waypoints[0] != 0 || + tidied.parameters.waypoints.back() != (tidied.parameters.coordinates.size() - 1))) + { + return Error("InvalidValue", + "First and last coordinates must be specified as waypoints.", + json_result); + } + // assuming radius is the standard deviation of a normal distribution // that models GPS noise (in this model), x3 should give us the correct // search radius with > 99% confidence @@ -229,6 +240,30 @@ Status MatchPlugin::HandleRequest(const RoutingAlgorithmsInterface &algorithms, return Error("NoMatch", "Could not match the trace.", json_result); } + // trace was split, we don't support the waypoints parameter across multiple match objects + if (sub_matchings.size() > 1 && !parameters.waypoints.empty()) + { + return Error("NoMatch", "Could not match the trace with the given waypoints.", json_result); + } + + // Error: Check if user-supplied waypoints can be found in the resulting matches + { + std::set tidied_waypoints(tidied.parameters.waypoints.begin(), + tidied.parameters.waypoints.end()); + for (const auto &sm : sub_matchings) + { + std::for_each(sm.indices.begin(), + sm.indices.end(), + [&tidied_waypoints](const auto index) { tidied_waypoints.erase(index); }); + } + if (!tidied_waypoints.empty()) + { + return Error( + "NoMatch", "Requested waypoint parameter could not be matched.", json_result); + } + } + + // each sub_route will correspond to a MatchObject std::vector sub_routes(sub_matchings.size()); for (auto index : util::irange(0UL, sub_matchings.size())) { @@ -245,12 +280,31 @@ Status MatchPlugin::HandleRequest(const RoutingAlgorithmsInterface &algorithms, BOOST_ASSERT(current_phantom_node_pair.target_phantom.IsValid()); sub_routes[index].segment_end_coordinates.emplace_back(current_phantom_node_pair); } - // force uturns to be on, since we split the phantom nodes anyway and only have - // bi-directional - // phantom nodes for possible uturns + // force uturns to be on + // we split the phantom nodes anyway and only have bi-directional phantom nodes for + // possible uturns sub_routes[index] = algorithms.ShortestPathSearch(sub_routes[index].segment_end_coordinates, {false}); BOOST_ASSERT(sub_routes[index].shortest_path_weight != INVALID_EDGE_WEIGHT); + if (!tidied.parameters.waypoints.empty()) + { + std::vector waypoint_legs; + waypoint_legs.reserve(sub_matchings[index].indices.size()); + for (unsigned i = 0, j = 0; i < sub_matchings[index].indices.size(); ++i) + { + auto current_wp = tidied.parameters.waypoints[j]; + if (current_wp == sub_matchings[index].indices[i]) + { + waypoint_legs.push_back(true); + ++j; + } + else + { + waypoint_legs.push_back(false); + } + } + sub_routes[index] = CollapseInternalRouteResult(sub_routes[index], waypoint_legs); + } } api::MatchAPI match_api{facade, parameters, tidied}; diff --git a/unit_tests/engine/collapse_internal_route_result.cpp b/unit_tests/engine/collapse_internal_route_result.cpp new file mode 100644 index 000000000..ded391b61 --- /dev/null +++ b/unit_tests/engine/collapse_internal_route_result.cpp @@ -0,0 +1,148 @@ +#include "engine/internal_route_result.hpp" +#include "engine/phantom_node.hpp" + +#include +#include + +#include +#include +#include + +BOOST_AUTO_TEST_SUITE(collapse_test) + +using namespace osrm; +using namespace osrm::util; +using namespace osrm::engine; + +BOOST_AUTO_TEST_CASE(unchanged_collapse_route_result) +{ + PhantomNode source; + PhantomNode target; + source.forward_segment_id = {1, true}; + target.forward_segment_id = {6, true}; + PathData pathy{2, 17, false, 2, 3, 4, 5, 0, {}, 4, 2, {}, 2, {1.0}, {1.0}, false}; + PathData kathy{1, 16, false, 1, 2, 3, 4, 1, {}, 3, 1, {}, 1, {2.0}, {3.0}, false}; + InternalRouteResult one_leg_result; + one_leg_result.unpacked_path_segments = {{pathy, kathy}}; + one_leg_result.segment_end_coordinates = {PhantomNodes{source, target}}; + one_leg_result.source_traversed_in_reverse = {true}; + one_leg_result.target_traversed_in_reverse = {true}; + one_leg_result.shortest_path_weight = 50; + + auto collapsed = CollapseInternalRouteResult(one_leg_result, {true, true}); + BOOST_CHECK_EQUAL(one_leg_result.unpacked_path_segments[0].front().turn_via_node, + collapsed.unpacked_path_segments[0].front().turn_via_node); + BOOST_CHECK_EQUAL(one_leg_result.unpacked_path_segments[0].back().turn_via_node, + collapsed.unpacked_path_segments[0].back().turn_via_node); +} + +BOOST_AUTO_TEST_CASE(two_legs_to_one_leg) +{ + PathData pathy{2, 17, false, 2, 3, 4, 5, 0, {}, 4, 2, {}, 2, {1.0}, {1.0}, false}; + PathData kathy{1, 16, false, 1, 2, 3, 4, 1, {}, 3, 1, {}, 1, {2.0}, {3.0}, false}; + PathData cathy{3, 16, false, 1, 2, 3, 4, 1, {}, 3, 1, {}, 1, {2.0}, {3.0}, false}; + PhantomNode node_1; + PhantomNode node_2; + PhantomNode node_3; + node_1.forward_segment_id = {1, true}; + node_2.forward_segment_id = {6, true}; + node_3.forward_segment_id = {12, true}; + InternalRouteResult two_leg_result; + two_leg_result.unpacked_path_segments = {{pathy, kathy}, {kathy, cathy}}; + two_leg_result.segment_end_coordinates = {PhantomNodes{node_1, node_2}, + PhantomNodes{node_2, node_3}}; + two_leg_result.source_traversed_in_reverse = {true, false}; + two_leg_result.target_traversed_in_reverse = {true, false}; + two_leg_result.shortest_path_weight = 80; + + auto collapsed = CollapseInternalRouteResult(two_leg_result, {true, false, true, true}); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments.size(), 1); + BOOST_CHECK_EQUAL(collapsed.segment_end_coordinates.size(), 1); + BOOST_CHECK_EQUAL(collapsed.segment_end_coordinates[0].target_phantom.forward_segment_id.id, + 12); + BOOST_CHECK_EQUAL(collapsed.segment_end_coordinates[0].source_phantom.forward_segment_id.id, 1); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[0].size(), 3); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[0][0].turn_via_node, 2); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[0][1].turn_via_node, 1); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[0][2].turn_via_node, 3); +} + +BOOST_AUTO_TEST_CASE(three_legs_to_two_legs) +{ + PathData pathy{2, 17, false, 2, 3, 4, 5, 0, {}, 4, 2, {}, 2, {1.0}, {1.0}, false}; + PathData kathy{1, 16, false, 1, 2, 3, 4, 1, {}, 3, 1, {}, 1, {2.0}, {3.0}, false}; + PathData qathy{5, 16, false, 1, 2, 3, 4, 1, {}, 3, 1, {}, 1, {2.0}, {3.0}, false}; + PathData cathy{3, 16, false, 1, 2, 3, 4, 1, {}, 3, 1, {}, 1, {2.0}, {3.0}, false}; + PathData mathy{4, 18, false, 8, 9, 13, 4, 2, {}, 4, 2, {}, 2, {3.0}, {1.0}, false}; + PhantomNode node_1; + PhantomNode node_2; + PhantomNode node_3; + PhantomNode node_4; + node_1.forward_segment_id = {1, true}; + node_2.forward_segment_id = {6, true}; + node_3.forward_segment_id = {12, true}; + node_4.forward_segment_id = {18, true}; + InternalRouteResult three_leg_result; + three_leg_result.unpacked_path_segments = {std::vector{pathy, kathy}, + std::vector{kathy, qathy, cathy}, + std::vector{cathy, mathy}}; + three_leg_result.segment_end_coordinates = { + PhantomNodes{node_1, node_2}, PhantomNodes{node_2, node_3}, PhantomNodes{node_3, node_4}}; + three_leg_result.source_traversed_in_reverse = {true, false, true}, + three_leg_result.target_traversed_in_reverse = {true, false, true}, + three_leg_result.shortest_path_weight = 140; + + auto collapsed = CollapseInternalRouteResult(three_leg_result, {true, true, false, true}); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments.size(), 2); + BOOST_CHECK_EQUAL(collapsed.segment_end_coordinates.size(), 2); + BOOST_CHECK_EQUAL(collapsed.segment_end_coordinates[0].source_phantom.forward_segment_id.id, 1); + BOOST_CHECK_EQUAL(collapsed.segment_end_coordinates[0].target_phantom.forward_segment_id.id, 6); + BOOST_CHECK_EQUAL(collapsed.segment_end_coordinates[1].source_phantom.forward_segment_id.id, 6); + BOOST_CHECK_EQUAL(collapsed.segment_end_coordinates[1].target_phantom.forward_segment_id.id, + 18); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[0].size(), 2); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[1].size(), 4); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[0][0].turn_via_node, 2); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[0][1].turn_via_node, 1); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[1][0].turn_via_node, 1); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[1][1].turn_via_node, 5); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[1][2].turn_via_node, 3); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[1][3].turn_via_node, 4); +} + +BOOST_AUTO_TEST_CASE(two_legs_to_two_legs) +{ + PathData pathy{2, 17, false, 2, 3, 4, 5, 0, {}, 4, 2, {}, 2, {1.0}, {1.0}, false}; + PathData kathy{1, 16, false, 1, 2, 3, 4, 1, {}, 3, 1, {}, 1, {2.0}, {3.0}, false}; + PathData cathy{3, 16, false, 1, 2, 3, 4, 1, {}, 3, 1, {}, 1, {2.0}, {3.0}, false}; + PhantomNode node_1; + PhantomNode node_2; + PhantomNode node_3; + node_1.forward_segment_id = {1, true}; + node_2.forward_segment_id = {6, true}; + node_3.forward_segment_id = {12, true}; + InternalRouteResult two_leg_result; + two_leg_result.unpacked_path_segments = {{pathy, kathy}, {kathy, cathy}}; + two_leg_result.segment_end_coordinates = {PhantomNodes{node_1, node_2}, + PhantomNodes{node_2, node_3}}; + two_leg_result.source_traversed_in_reverse = {true, false}; + two_leg_result.target_traversed_in_reverse = {true, false}; + two_leg_result.shortest_path_weight = 80; + + auto collapsed = CollapseInternalRouteResult(two_leg_result, {true, true, true}); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments.size(), 2); + BOOST_CHECK_EQUAL(collapsed.segment_end_coordinates.size(), 2); + BOOST_CHECK_EQUAL(collapsed.segment_end_coordinates[0].source_phantom.forward_segment_id.id, 1); + BOOST_CHECK_EQUAL(collapsed.segment_end_coordinates[0].target_phantom.forward_segment_id.id, 6); + BOOST_CHECK_EQUAL(collapsed.segment_end_coordinates[1].source_phantom.forward_segment_id.id, 6); + BOOST_CHECK_EQUAL(collapsed.segment_end_coordinates[1].target_phantom.forward_segment_id.id, + 12); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[0].size(), 2); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[1].size(), 2); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[0][0].turn_via_node, 2); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[0][1].turn_via_node, 1); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[1][0].turn_via_node, 1); + BOOST_CHECK_EQUAL(collapsed.unpacked_path_segments[1][1].turn_via_node, 3); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unit_tests/server/parameters_parser.cpp b/unit_tests/server/parameters_parser.cpp index 7318b72fe..f7358500e 100644 --- a/unit_tests/server/parameters_parser.cpp +++ b/unit_tests/server/parameters_parser.cpp @@ -555,6 +555,22 @@ BOOST_AUTO_TEST_CASE(valid_match_urls) CHECK_EQUAL_RANGE(reference_2.radiuses, result_2->radiuses); CHECK_EQUAL_RANGE(reference_2.approaches, result_2->approaches); CHECK_EQUAL_RANGE(reference_2.coordinates, result_2->coordinates); + + std::vector coords_2 = {{util::FloatLongitude{1}, util::FloatLatitude{2}}, + {util::FloatLongitude{3}, util::FloatLatitude{4}}, + {util::FloatLongitude{5}, util::FloatLatitude{6}}}; + + MatchParameters reference_3{}; + reference_3.coordinates = coords_2; + reference_3.waypoints = {0, 2}; + auto result_3 = parseParameters("1,2;3,4;5,6?waypoints=0;2"); + BOOST_CHECK(result_3); + CHECK_EQUAL_RANGE(reference_3.waypoints, result_3->waypoints); + CHECK_EQUAL_RANGE(reference_3.timestamps, result_3->timestamps); + CHECK_EQUAL_RANGE(reference_3.bearings, result_3->bearings); + CHECK_EQUAL_RANGE(reference_3.radiuses, result_3->radiuses); + CHECK_EQUAL_RANGE(reference_3.approaches, result_3->approaches); + CHECK_EQUAL_RANGE(reference_3.coordinates, result_3->coordinates); } BOOST_AUTO_TEST_CASE(invalid_match_urls) @@ -571,6 +587,15 @@ BOOST_AUTO_TEST_CASE(invalid_match_urls) BOOST_CHECK(reference_1.radiuses != result_1->radiuses); CHECK_EQUAL_RANGE(reference_1.approaches, result_1->approaches); CHECK_EQUAL_RANGE(reference_1.coordinates, result_1->coordinates); + + std::vector coords_2 = {{util::FloatLongitude{1}, util::FloatLatitude{2}}, + {util::FloatLongitude{3}, util::FloatLatitude{4}}}; + + MatchParameters reference_2{}; + reference_2.coordinates = coords_2; + BOOST_CHECK_EQUAL(testInvalidOptions("1,2;3,4?waypoints=0,4"), 19UL); + BOOST_CHECK_EQUAL(testInvalidOptions("1,2;3,4?waypoints=x;4"), 18UL); + BOOST_CHECK_EQUAL(testInvalidOptions("1,2;3,4?waypoints=0;3.5"), 21UL); } BOOST_AUTO_TEST_CASE(valid_nearest_urls)