Add waypoints parameter to matching service, returns map matching result

with selective input coordinates as waypoints
This commit is contained in:
karenzshea 2017-12-07 16:36:30 +01:00 committed by Karen Shea
parent 8883d8cc56
commit 55cc06fd8b
11 changed files with 522 additions and 12 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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 |

View File

@ -86,6 +86,10 @@ class MatchAPI final : public RouteAPI
for (auto point_index : util::irange(
0u, static_cast<unsigned>(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<std::size_t>(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));
}

View File

@ -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 <typename... Args>
MatchParameters(std::vector<unsigned> timestamps_, GapsType gaps_, bool tidy_, Args... args_)
: MatchParameters(std::move(timestamps_), gaps_, tidy_, {}, std::forward<Args>(args_)...)
{
}
template <typename... Args>
MatchParameters(std::vector<unsigned> timestamps_,
GapsType gaps_,
bool tidy_,
std::vector<std::size_t> waypoints_,
Args... args_)
: RouteParameters{std::forward<Args>(args_)...}, timestamps{std::move(timestamps_)},
gaps(gaps_), tidy(tidy_)
gaps(gaps_), tidy(tidy_), waypoints{std::move(waypoints_)}
{
}
std::vector<unsigned> timestamps;
GapsType gaps;
bool tidy;
std::vector<std::size_t> 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;
}
};
}

View File

@ -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 &params)
@ -44,6 +47,15 @@ inline Result keep_all(const MatchParameters &params)
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 &params)
{
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 &params)
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 &params, 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 &params, 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 &params, 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;
}

View File

@ -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 <vector>
@ -102,6 +102,56 @@ struct InternalManyRoutesResult
std::vector<InternalRouteResult> routes;
};
inline InternalRouteResult CollapseInternalRouteResult(const InternalRouteResult &leggy_result,
const std::vector<bool> &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<std::size_t>(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;
}
}
}

View File

@ -28,17 +28,31 @@ struct MatchParametersGrammar final : public RouteParametersGrammar<Iterator, Si
MatchParametersGrammar() : BaseGrammar(root_rule)
{
#ifdef BOOST_HAS_LONG_LONG
if (std::is_same<std::size_t, unsigned long long>::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<Iterator, Si
private:
qi::rule<Iterator, Signature> root_rule;
qi::rule<Iterator, Signature> timestamps_rule;
qi::rule<Iterator, Signature> waypoints_rule;
qi::rule<Iterator, std::size_t()> size_t_;
qi::symbols<char, engine::api::MatchParameters::GapsType> gaps_type;
};

View File

@ -17,6 +17,7 @@
#include <functional>
#include <iterator>
#include <memory>
#include <set>
#include <string>
#include <vector>
@ -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<std::size_t> 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<InternalRouteResult> sub_routes(sub_matchings.size());
for (auto index : util::irange<std::size_t>(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<bool> 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};

View File

@ -0,0 +1,148 @@
#include "engine/internal_route_result.hpp"
#include "engine/phantom_node.hpp"
#include <boost/test/test_case_template.hpp>
#include <boost/test/unit_test.hpp>
#include <algorithm>
#include <iterator>
#include <vector>
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<PathData>{pathy, kathy},
std::vector<PathData>{kathy, qathy, cathy},
std::vector<PathData>{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()

View File

@ -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<util::Coordinate> 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<MatchParameters>("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<util::Coordinate> 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<MatchParameters>("1,2;3,4?waypoints=0,4"), 19UL);
BOOST_CHECK_EQUAL(testInvalidOptions<MatchParameters>("1,2;3,4?waypoints=x;4"), 18UL);
BOOST_CHECK_EQUAL(testInvalidOptions<MatchParameters>("1,2;3,4?waypoints=0;3.5"), 21UL);
}
BOOST_AUTO_TEST_CASE(valid_nearest_urls)