Trip with Fixed Start and End points (TFSE) (#3408)

* fixed start and end trip feature to trip service
This commit is contained in:
Kajari Ghosh 2017-02-10 05:13:20 -05:00 committed by GitHub
parent 3e2db47cc8
commit 2218658969
15 changed files with 895 additions and 277 deletions

View File

@ -30,6 +30,8 @@
- libOSRM now creates an own watcher thread then used in shared memory mode to listen for data updates
- Tools:
- Added osrm-extract-conditionals tool for checking conditional values in OSM data
- Trip Plugin
- Added a new feature that finds the optimal route given a list of waypoints, a source and a destination. This does not return a roundtrip and instead returns a one way optimal route from the fixed source to the destination points.
# 5.5.1
- Changes from 5.5.0

View File

@ -311,27 +311,62 @@ All other properties might be undefined.
### Trip service
The trip plugin solves the Traveling Salesman Problem using a greedy heuristic (farthest-insertion algorithm).
The returned path does not have to be the fastest path, as TSP is NP-hard it is only an approximation.
Note that if the input coordinates can not be joined by a single trip (e.g. the coordinates are on several disconnected islands)
multiple trips for each connected component are returned.
The trip plugin solves the Traveling Salesman Problem using a greedy heuristic (farthest-insertion algorithm) for 10 or more waypoints and uses brute force for less than 10 waypoints.
The returned path does not have to be the fastest path. As TSP is NP-hard it only returns an approximation.
Note that all input coordinates have to be connected for the trip service to work.
```endpoint
GET /trip/v1/{profile}/{coordinates}?steps={true|false}&geometries={polyline|polyline6|geojson}&overview={simplified|full|false}&annotations={true|false}'
GET /trip/v1/{profile}/{coordinates}?roundtrip={true|false}&source{any|first}&destination{any|last}&steps={true|false}&geometries={polyline|polyline6|geojson}&overview={simplified|full|false}&annotations={true|false}'
```
In addition to the [general options](#general-options) the following options are supported for this service:
|Option |Values |Description |
|------------|------------------------------------------------|---------------------------------------------------------------------------|
|roundtrip |`true` (default), `false` |Return route is a roundtrip |
|source |`any` (default), `first` |Return route starts at `any` or `first` coordinate |
|destination |`any` (default), `last` |Return route ends at `any` or `last` coordinate |
|steps |`true`, `false` (default) |Return route instructions for each trip |
|annotations |`true`, `false` (default) |Returns additional metadata for each coordinate along the route geometry. |
|geometries |`polyline` (default), `polyline6`, `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.|
**Response**
**Fixing Start and End Points**
- `code` if the request was successful `Ok` otherwise see the service dependent and general status codes.
It is possible to explicitely set the start or end coordinate of the trip.
When source is set to `first`, the first coordinate is used as start coordinate of the trip in the output. When destination is set to `last`, the last coordinate will be used as destination of the trip in the returned output. If you specify `any`, any of the coordinates can be used as the first or last coordinate in the output.
However, if `source=any&destination=any` the returned round-trip will still start at the first input coordinate by default.
Currently, not all combinations of `roundtrip`, `source` and `destination` are supported.
Right now, the following combinations are possible:
| roundtrip | source | destination | supported |
| :-- | :-- | :-- | :-- |
| true | first | last | **yes** |
| true | first | any | **yes** |
| true | any | last | **yes** |
| true | any | any | **yes** |
| false | first | last | **yes** |
| false | first | any | no |
| false | any | last | no |
| false | any | any | no |
#### Example Requests
```curl
# Round trip in Berlin with three stops:
curl 'http://router.project-osrm.org/trip/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219'
```
```curl
# Round trip in Berlin with four stops, starting at the first stop, ending at the last:
curl 'http://router.project-osrm.org/trip/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219;13.418555,52.523215?source=first&destination=last'
```
#### Response
- `code`: if the request was successful `Ok` otherwise see the service dependent and general status codes.
- `waypoints`: Array of `Waypoint` objects representing all waypoints in input order. Each `Waypoint` object has the following additional properties:
- `trips_index`: Index to `trips` of the sub-trip the point was matched to.
- `waypoint_index`: Index of the point in the trip.
@ -341,7 +376,8 @@ In case of error the following `code`s are supported in addition to the general
| Type | Description |
|-------------------|---------------------|
| `NoTrips` | No trips found. |
| `NoTrips` | No trips found because input coordinates are not connected.|
| `NotImplemented` | This request is not supported |
All other properties might be undefined.

View File

@ -24,7 +24,6 @@ module.exports = function () {
} else if (row[k]) {
params[match[1]] = [row[k]];
}
got[k] = row[k];
}
}
@ -35,11 +34,11 @@ module.exports = function () {
}
if (headers.has('status')) {
got.status = json.status.toString();
got.status = json.code;
}
if (headers.has('message')) {
got.message = json.status_message;
got.message = json.message;
}
if (headers.has('geometry')) {
@ -59,6 +58,7 @@ module.exports = function () {
var subTrips;
var trip_durations;
var trip_distance;
if (res.statusCode === 200) {
if (headers.has('trips')) {
subTrips = json.trips.filter(t => !!t).map(t => t.legs).map(tl => Array.prototype.concat.apply([], tl.map((sl, i) => {
@ -74,13 +74,19 @@ module.exports = function () {
})));
trip_durations = all_durations.map( a => a.reduce(add, 0));
}
if(headers.has('distance')) {
var all_distance = json.trips.filter(t => !!t).map(t => t.legs).map(tl => Array.prototype.concat.apply([], tl.map(sl => {
return sl.distance;
})));
trip_distance = all_distance.map( a => a.reduce(add, 0));
}
}
var ok = true,
encodedResult = '',
extendedTarget = '';
row.trips.split(',').forEach((sub, si) => {
if (json.trips) row.trips.split(',').forEach((sub, si) => {
if (si >= subTrips.length) {
ok = false;
} else {
@ -105,10 +111,10 @@ module.exports = function () {
got.via_points = row.via_points;
} else {
got.trips = encodedResult;
got.trips = extendedTarget;
}
got.durations = trip_durations;
got.distance = trip_distance;
for (var key in row) {
if (this.FuzzyMatch.match(got[key], row[key])) {
@ -144,6 +150,19 @@ module.exports = function () {
waypoints.push(node);
});
got = { waypoints: row.waypoints };
if (row.source) {
params.source = got.source = row.source;
}
if (row.destination) {
params.destination = got.destination = row.destination;
}
if (row.hasOwnProperty('roundtrip')) { //roundtrip is a boolean so row.roundtrip alone doesn't work as a check here
params.roundtrip = got.roundtrip = row.roundtrip;
}
this.requestTrip(waypoints, params, afterRequest);
} else {
throw new Error('*** no waypoints');

View File

@ -5,7 +5,25 @@ Feature: Basic trip planning
Given the profile "testbot"
Given a grid size of 10 meters
Scenario: Testbot - Trip planning with less than 10 nodes
Scenario: Testbot - Trip: Roundtrip with one waypoint
Given the node map
"""
a b
c d
"""
And the ways
| nodes |
| ab |
| bc |
| cb |
| da |
When I plan a trip I should get
| waypoints | trips |
| a | aa |
Scenario: Testbot - Trip: Roundtrip with waypoints (less than 10)
Given the node map
"""
a b
@ -24,7 +42,7 @@ Feature: Basic trip planning
| a,b,c,d | abcda | 7.6 |
| d,b,c,a | dbcad | 7.6 |
Scenario: Testbot - Trip planning with more than 10 nodes
Scenario: Testbot - Trip: Roundtrip waypoints (more than 10)
Given the node map
"""
a b c d
@ -37,7 +55,6 @@ Feature: Basic trip planning
| nodes |
| ab |
| bc |
| cb |
| de |
| ef |
| fg |
@ -48,12 +65,85 @@ Feature: Basic trip planning
| kl |
| la |
When I plan a trip I should get
| waypoints | trips |
| a,b,c,d,e,f,g,h,i,j,k,l | alkjihgfedcba |
Scenario: Testbot - Trip: Roundtrip FS waypoints (more than 10)
Given the node map
"""
a b c d
l e
k f
j i h g
"""
And the ways
| nodes |
| ab |
| bc |
| de |
| ef |
| fg |
| gh |
| hi |
| ij |
| jk |
| kl |
| la |
When I plan a trip I should get
| waypoints | source | trips |
| a,b,c,d,e,f,g,h,i,j,k,l | first | alkjihgfedcba |
Scenario: Testbot - Trip: Roundtrip FE waypoints (more than 10)
Given the query options
| source | last |
Given the node map
"""
a b c d
l e
k f
j i h g
"""
And the ways
| nodes |
| ab |
| bc |
| de |
| ef |
| fg |
| gh |
| hi |
| ij |
| jk |
| kl |
| la |
When I plan a trip I should get
| waypoints | trips |
| a,b,c,d,e,f,g,h,i,j,k,l | cbalkjihgfedc |
| a,b,c,d,e,f,g,h,i,j,k,l | lkjihgfedcbal |
Scenario: Testbot - Trip planning with multiple scc
Scenario: Testbot - Trip: Unroutable roundtrip with waypoints (less than 10)
Given the node map
"""
a b
d c
"""
And the ways
| nodes |
| ab |
| dc |
When I plan a trip I should get
| waypoints | status | message |
| a,b,c,d | NoTrips | No trip visiting all destinations possible. |
Scenario: Testbot - Trip: Unroutable roundtrip with waypoints (more than 10)
Given the node map
"""
a b c d
@ -85,13 +175,106 @@ Feature: Basic trip planning
| pq |
| qm |
When I plan a trip I should get
| waypoints | status | message |
| a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p | NoTrips | No trip visiting all destinations possible. |
# Test TFSE
Scenario: Testbot - Trip: TFSE with errors
Given the node map
"""
a b
d c
"""
And the ways
| nodes |
| ab |
| dc |
When I plan a trip I should get
| waypoints | trips |
| a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p | defghijklabcd,mnopm |
| waypoints | source | destination | roundtrip | status | message |
| a,b,c,d | first | last | false | NoTrips | No trip visiting all destinations possible. |
# Test single node in each component #1850
Scenario: Testbot - Trip planning with less than 10 nodes
Scenario: Testbot - Trip: FSE with waypoints (less than 10)
Given the node map
"""
a b
c
e d
"""
And the ways
| nodes |
| ab |
| ac |
| ad |
| ae |
| bc |
| bd |
| be |
| cd |
| ce |
| de |
When I plan a trip I should get
| waypoints | source | destination |roundtrip | trips | durations | distance |
| a,b,d,e,c | first | last | false | abedc | 8.200000000000001 | 81.6 |
Scenario: Testbot - Trip: FSE with waypoints (more than 10)
Given the node map
"""
a b c d e f g h i j k
"""
And the ways
| nodes |
| ab |
| bc |
| cd |
| de |
| ef |
| fg |
| gh |
| hi |
| ij |
| jk |
When I plan a trip I should get
| waypoints | source | destination | roundtrip | trips | durations | distance |
| a,b,c,d,e,h,i,j,k,g,f | first | last | false | abcdeghijkf | 15 | 149.8 |
Scenario: Testbot - Trip: FSE roundtrip with waypoints (less than 10)
Given the node map
"""
a b
c
e d
"""
And the ways
| nodes |
| ab |
| ac |
| ad |
| ae |
| bc |
| bd |
| be |
| cd |
| ce |
| de |
When I plan a trip I should get
| waypoints | source | destination | roundtrip | trips |
| a,b,d,e,c | first | last | true | abedca |
Scenario: Testbot - Trip: midway points in isoldated roads should return no trips
Given the node map
"""
a 1 b
@ -105,10 +288,10 @@ Feature: Basic trip planning
| cd |
When I plan a trip I should get
| waypoints | trips |
| 1,2 | |
| waypoints | status | message |
| 1,2 | NoTrips | No trip visiting all destinations possible. |
Scenario: Testbot - Repeated Coordinate
Scenario: Testbot - Trip: Repeated Coordinate
Given the node map
"""
a b
@ -123,7 +306,7 @@ Feature: Basic trip planning
| a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a | |
Scenario: Testbot - Trip with geometry details of geojson
Scenario: Testbot - Trip: geometry details of geojson
Given the query options
| geometries | geojson |
@ -145,7 +328,7 @@ Feature: Basic trip planning
| a,b,c,d | abcda | 7.6 | 1,1,1.00009,1,1,0.99991,1.00009,1,1,1,1.00009,0.99991,1,1 |
| d,b,c,a | dbcad | 7.6 | 1.00009,0.99991,1,1,1.00009,1,1,0.99991,1.00009,1,1,1,1.00009,0.99991 |
Scenario: Testbot - Trip with geometry details of polyline
Scenario: Testbot - Trip: geometry details of polyline
Given the query options
| geometries | polyline |
@ -167,7 +350,7 @@ Feature: Basic trip planning
| a,b,c,d | abcda | 7.6 | 1,1,1,1.00009,0.99991,1,1,1.00009,1,1,0.99991,1.00009,1,1 |
| d,b,c,a | dbcad | 7.6 | 0.99991,1.00009,1,1,1,1.00009,0.99991,1,1,1.00009,1,1,0.99991,1.00009 |
Scenario: Testbot - Trip with geometry details of polyline6
Scenario: Testbot - Trip: geometry details of polyline6
Given the query options
| geometries | polyline6 |

View File

@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "engine/api/route_parameters.hpp"
#include <boost/optional.hpp>
#include <vector>
namespace osrm
@ -47,7 +48,33 @@ namespace api
*/
struct TripParameters : public RouteParameters
{
// bool IsValid() const; Falls back to base class
TripParameters() = default;
enum class SourceType
{
Any,
First
};
enum class DestinationType
{
Any,
Last
};
template <typename... Args>
TripParameters(SourceType source_,
DestinationType destination_,
bool roundtrip_,
Args &&... args_)
: RouteParameters{std::forward<Args>(args_)...}, source{source_}, destination{destination_},
roundtrip{roundtrip_}
{
}
SourceType source = SourceType::Any;
DestinationType destination = DestinationType::Any;
bool roundtrip = true;
bool IsValid() const { return RouteParameters::IsValid(); }
};
}
}

View File

@ -36,7 +36,8 @@ class TripPlugin final : public BasePlugin
InternalRouteResult ComputeRoute(const std::shared_ptr<const datafacade::BaseDataFacade> facade,
const std::vector<PhantomNode> &phantom_node_list,
const std::vector<NodeID> &trip) const;
const std::vector<NodeID> &trip,
const bool roundtrip) const;
public:
explicit TripPlugin(const int max_locations_trip_)

View File

@ -11,6 +11,7 @@
#include <cstdlib>
#include <iterator>
#include <limits>
#include <numeric>
#include <string>
#include <vector>
@ -25,62 +26,76 @@ namespace trip
EdgeWeight ReturnDistance(const util::DistTableWrapper<EdgeWeight> &dist_table,
const std::vector<NodeID> &location_order,
const EdgeWeight min_route_dist,
const std::size_t component_size)
const std::size_t number_of_locations)
{
EdgeWeight route_dist = 0;
std::size_t i = 0;
while (i < location_order.size() && (route_dist < min_route_dist))
std::size_t current_index = 0;
while (current_index < location_order.size() && (route_dist < min_route_dist))
{
route_dist += dist_table(location_order[i], location_order[(i + 1) % component_size]);
BOOST_ASSERT_MSG(dist_table(location_order[i], location_order[(i + 1) % component_size]) !=
std::size_t next_index = (current_index + 1) % number_of_locations;
auto edge_weight = dist_table(location_order[current_index], location_order[next_index]);
// If the edge_weight is very large (INVALID_EDGE_WEIGHT) then the algorithm will not choose
// this edge in final minimal path. So instead of computing all the permutations after this
// large edge, discard this edge right here and don't consider the path after this edge.
if (edge_weight == INVALID_EDGE_WEIGHT)
{
return INVALID_EDGE_WEIGHT;
}
else
{
route_dist += edge_weight;
}
// This boost assert should not be reached if TFSE table
BOOST_ASSERT_MSG(dist_table(location_order[current_index], location_order[next_index]) !=
INVALID_EDGE_WEIGHT,
"invalid route found");
++i;
++current_index;
}
return route_dist;
}
// computes the route by computing all permutations and selecting the shortest
template <typename NodeIDIterator>
std::vector<NodeID> BruteForceTrip(const NodeIDIterator start,
const NodeIDIterator end,
const std::size_t number_of_locations,
std::vector<NodeID> BruteForceTrip(const std::size_t number_of_locations,
const util::DistTableWrapper<EdgeWeight> &dist_table)
{
(void)number_of_locations; // unused
const auto component_size = std::distance(start, end);
std::vector<NodeID> perm(start, end);
std::vector<NodeID> route = perm;
// set initial order in which nodes are visited to 0, 1, 2, 3, ...
std::vector<NodeID> node_order(number_of_locations);
std::iota(std::begin(node_order), std::end(node_order), 0);
std::vector<NodeID> route = node_order;
EdgeWeight min_route_dist = INVALID_EDGE_WEIGHT;
// check length of all possible permutation of the component ids
BOOST_ASSERT_MSG(perm.size() > 0, "no permutation given");
BOOST_ASSERT_MSG(*(std::max_element(std::begin(perm), std::end(perm))) < number_of_locations,
BOOST_ASSERT_MSG(node_order.size() > 0, "no order permutation given");
BOOST_ASSERT_MSG(*(std::max_element(std::begin(node_order), std::end(node_order))) <
number_of_locations,
"invalid node id");
BOOST_ASSERT_MSG(*(std::min_element(std::begin(node_order), std::end(node_order))) >= 0,
"invalid node id");
BOOST_ASSERT_MSG(*(std::min_element(std::begin(perm), std::end(perm))) >= 0, "invalid node id");
do
{
const auto new_distance = ReturnDistance(dist_table, perm, min_route_dist, component_size);
const auto new_distance =
ReturnDistance(dist_table, node_order, min_route_dist, number_of_locations);
// we can use `<` instead of `<=` here, since all distances are `!=` INVALID_EDGE_WEIGHT
// In case we really sum up to invalid edge weight for all permutations, keeping the very
// first one is fine too.
if (new_distance < min_route_dist)
{
min_route_dist = new_distance;
route = perm;
route = node_order;
}
} while (std::next_permutation(std::begin(perm), std::end(perm)));
} while (std::next_permutation(std::begin(node_order), std::end(node_order)));
return route;
}
}
}
}
} // namespace trip
} // namespace engine
} // namespace osrm
#endif // TRIP_BRUTE_FORCE_HPP

View File

@ -50,8 +50,11 @@ GetShortestRoundTrip(const NodeID new_loc,
const auto dist_to = dist_table(new_loc, *to_node);
const auto trip_dist = dist_from + dist_to - dist_table(*from_node, *to_node);
BOOST_ASSERT_MSG(dist_from != INVALID_EDGE_WEIGHT, "distance has invalid edge weight");
BOOST_ASSERT_MSG(dist_to != INVALID_EDGE_WEIGHT, "distance has invalid edge weight");
// If the edge_weight is very large (INVALID_EDGE_WEIGHT) then the algorithm will not choose
// this edge in final minimal path. So instead of computing all the permutations after this
// large edge, discard this edge right here and don't consider the path after this edge.
if (dist_from == INVALID_EDGE_WEIGHT || dist_to == INVALID_EDGE_WEIGHT)
continue;
// This is not neccessarily true:
// Lets say you have an edge (u, v) with duration 100. If you place a coordinate exactly in
// the middle of the segment yielding (u, v'), the adjusted duration will be 100 * 0.5 = 50.
@ -72,18 +75,14 @@ GetShortestRoundTrip(const NodeID new_loc,
return std::make_pair(min_trip_distance, next_insert_point_candidate);
}
template <typename NodeIDIterator>
// given two initial start nodes, find a roundtrip route using the farthest insertion algorithm
std::vector<NodeID> FindRoute(const std::size_t &number_of_locations,
const std::size_t &component_size,
const NodeIDIterator &start,
const NodeIDIterator &end,
const util::DistTableWrapper<EdgeWeight> &dist_table,
const NodeID &start1,
const NodeID &start2)
{
BOOST_ASSERT_MSG(number_of_locations >= component_size,
"component size bigger than total number of locations");
BOOST_ASSERT_MSG(number_of_locations * number_of_locations == dist_table.size(),
"number_of_locations and dist_table size do not match");
std::vector<NodeID> route;
route.reserve(number_of_locations);
@ -96,22 +95,21 @@ std::vector<NodeID> FindRoute(const std::size_t &number_of_locations,
route.push_back(start1);
route.push_back(start2);
// add all other nodes missing (two nodes are already in the initial start trip)
for (std::size_t j = 2; j < component_size; ++j)
// two nodes are already in the initial start trip, so we need to add all other nodes
for (std::size_t added_nodes = 2; added_nodes < number_of_locations; ++added_nodes)
{
auto farthest_distance = std::numeric_limits<int>::min();
auto next_node = -1;
NodeIDIter next_insert_point;
// find unvisited loc i that is the farthest away from all other visited locs
for (auto i = start; i != end; ++i)
// find unvisited node that is the farthest away from all other visited locs
for (std::size_t id = 0; id < number_of_locations; ++id)
{
// find the shortest distance from i to all visited nodes
if (!visited[*i])
if (!visited[id])
{
const auto insert_candidate =
GetShortestRoundTrip(*i, dist_table, number_of_locations, route);
GetShortestRoundTrip(id, dist_table, number_of_locations, route);
BOOST_ASSERT_MSG(insert_candidate.first != INVALID_EDGE_WEIGHT,
"shortest round trip is invalid");
@ -121,7 +119,7 @@ std::vector<NodeID> FindRoute(const std::size_t &number_of_locations,
if (insert_candidate.first > farthest_distance)
{
farthest_distance = insert_candidate.first;
next_node = *i;
next_node = id;
next_insert_point = insert_candidate.second;
}
}
@ -136,10 +134,7 @@ std::vector<NodeID> FindRoute(const std::size_t &number_of_locations,
return route;
}
template <typename NodeIDIterator>
std::vector<NodeID> FarthestInsertionTrip(const NodeIDIterator &start,
const NodeIDIterator &end,
const std::size_t number_of_locations,
std::vector<NodeID> FarthestInsertionTrip(const std::size_t number_of_locations,
const util::DistTableWrapper<EdgeWeight> &dist_table)
{
//////////////////////////////////////////////////////////////////////////////////////////////////
@ -158,57 +153,29 @@ std::vector<NodeID> FarthestInsertionTrip(const NodeIDIterator &start,
// Guard against dist_table being empty therefore max_element returning the end iterator.
BOOST_ASSERT(dist_table.size() > 0);
const auto component_size = std::distance(start, end);
BOOST_ASSERT(component_size >= 0);
BOOST_ASSERT_MSG(number_of_locations * number_of_locations == dist_table.size(),
"number_of_locations and dist_table size do not match");
auto max_from = -1;
auto max_to = -1;
if (static_cast<std::size_t>(component_size) == number_of_locations)
{
// find the pair of location with the biggest distance and make the pair the initial start
// trip. Skipping over the very first element (0,0), we make sure not to end up with the
// same start/end in the special case where all entries are the same.
const auto index =
std::distance(std::begin(dist_table),
std::max_element(std::begin(dist_table) + 1, std::end(dist_table)));
max_from = index / number_of_locations;
max_to = index % number_of_locations;
}
else
{
auto max_dist = std::numeric_limits<EdgeWeight>::min();
for (auto x = start; x != end; ++x)
{
for (auto y = start; y != end; ++y)
{
// don't repeat coordinates
if (*x == *y)
continue;
const auto xy_dist = dist_table(*x, *y);
// SCC decomposition done correctly?
BOOST_ASSERT(xy_dist != INVALID_EDGE_WEIGHT);
if (xy_dist >= max_dist)
{
max_dist = xy_dist;
max_from = *x;
max_to = *y;
}
}
}
}
const auto index_of_farthest_distance = std::distance(
std::begin(dist_table), std::max_element(std::begin(dist_table) + 1, std::end(dist_table)));
// distance table is a nxn matrix with the distance(u,v) in column u and row v
// but the distance table is stored in an 1D array of distances
// to get the actual (u,v), get the row by dividing and the column by computing modulo n
NodeID max_from = index_of_farthest_distance / number_of_locations;
NodeID max_to = index_of_farthest_distance % number_of_locations;
BOOST_ASSERT(max_from >= 0);
BOOST_ASSERT(max_to >= 0);
BOOST_ASSERT_MSG(static_cast<std::size_t>(max_from) < number_of_locations, "start node");
BOOST_ASSERT_MSG(static_cast<std::size_t>(max_to) < number_of_locations, "start node");
return FindRoute(number_of_locations, component_size, start, end, dist_table, max_from, max_to);
}
}
}
return FindRoute(number_of_locations, dist_table, max_from, max_to);
}
} // namespace trip
} // namespace engine
} // namespace osrm
#endif // TRIP_FARTHEST_INSERTION_HPP

View File

@ -4,6 +4,7 @@
#include "server/api/route_parameters_grammar.hpp"
#include "engine/api/trip_parameters.hpp"
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
namespace osrm
@ -15,6 +16,7 @@ namespace api
namespace
{
namespace ph = boost::phoenix;
namespace qi = boost::spirit::qi;
}
@ -26,12 +28,37 @@ struct TripParametersGrammar final : public RouteParametersGrammar<Iterator, Sig
TripParametersGrammar() : BaseGrammar(root_rule)
{
roundtrip_rule =
qi::lit("roundtrip=") >
qi::bool_[ph::bind(&engine::api::TripParameters::roundtrip, qi::_r1) = qi::_1];
source_type.add("any", engine::api::TripParameters::SourceType::Any)(
"first", engine::api::TripParameters::SourceType::First);
destination_type.add("any", engine::api::TripParameters::DestinationType::Any)(
"last", engine::api::TripParameters::DestinationType::Last);
source_rule = qi::lit("source=") >
source_type[ph::bind(&engine::api::TripParameters::source, qi::_r1) = qi::_1];
destination_rule =
qi::lit("destination=") >
destination_type[ph::bind(&engine::api::TripParameters::destination, qi::_r1) = qi::_1];
root_rule = BaseGrammar::query_rule(qi::_r1) > -qi::lit(".json") >
-('?' > (BaseGrammar::base_rule(qi::_r1)) % '&');
-('?' > (roundtrip_rule(qi::_r1) | source_rule(qi::_r1) |
destination_rule(qi::_r1) | BaseGrammar::base_rule(qi::_r1)) %
'&');
}
private:
qi::rule<Iterator, Signature> source_rule;
qi::rule<Iterator, Signature> destination_rule;
qi::rule<Iterator, Signature> roundtrip_rule;
qi::rule<Iterator, Signature> root_rule;
qi::symbols<char, engine::api::TripParameters::SourceType> source_type;
qi::symbols<char, engine::api::TripParameters::DestinationType> destination_type;
};
}
}

View File

@ -46,6 +46,18 @@ template <typename T> class DistTableWrapper
return table_[index];
}
void SetValue(NodeID from, NodeID to, EdgeWeight value)
{
BOOST_ASSERT_MSG(from < number_of_nodes_, "from ID is out of bound");
BOOST_ASSERT_MSG(to < number_of_nodes_, "to ID is out of bound");
const auto index = from * number_of_nodes_ + to;
BOOST_ASSERT_MSG(index < table_.size(), "index is out of bound");
table_[index] = value;
}
ConstIterator begin() const { return std::begin(table_); }
Iterator begin() { return std::begin(table_); }

View File

@ -13,8 +13,7 @@ namespace util
{
// This Wrapper provides all methods that are needed for extractor::TarjanSCC, when the graph is
// given in a
// matrix representation (e.g. as output from a distance table call)
// given in a matrix representation (e.g. as output from a distance table call)
template <typename T> class MatrixGraphWrapper
{

View File

@ -60,6 +60,8 @@ using NameID = std::uint32_t;
using EdgeWeight = std::int32_t;
using TurnPenalty = std::int16_t; // turn penalty in 100ms units
static const std::size_t INVALID_INDEX = std::numeric_limits<std::size_t>::max();
using LaneID = std::uint8_t;
static const LaneID INVALID_LANEID = std::numeric_limits<LaneID>::max();
using LaneDataID = std::uint16_t;

View File

@ -1,7 +1,5 @@
#include "engine/plugins/trip.hpp"
#include "extractor/tarjan_scc.hpp"
#include "engine/api/trip_api.hpp"
#include "engine/api/trip_parameters.hpp"
#include "engine/trip/trip_brute_force.hpp"
@ -16,6 +14,7 @@
#include <algorithm>
#include <cstdlib>
#include <iterator>
#include <limits>
#include <memory>
#include <string>
#include <utility>
@ -28,129 +27,150 @@ namespace engine
namespace plugins
{
// Object to hold all strongly connected components (scc) of a graph
// to access all graphs with component ID i, get the iterators by:
// auto start = std::begin(scc_component.component) + scc_component.range[i];
// auto end = std::begin(scc_component.component) + scc_component.range[i+1];
struct SCC_Component
bool IsStronglyConnectedComponent(const util::DistTableWrapper<EdgeWeight> &result_table)
{
// in_component: all NodeIDs sorted by component ID
// in_range: index where a new component starts
//
// example: NodeID 0, 1, 2, 4, 5 are in component 0
// NodeID 3, 6, 7, 8 are in component 1
// => in_component = [0, 1, 2, 4, 5, 3, 6, 7, 8]
// => in_range = [0, 5]
SCC_Component(std::vector<NodeID> in_component_nodes, std::vector<size_t> in_range)
: component(std::move(in_component_nodes)), range(std::move(in_range))
{
BOOST_ASSERT_MSG(component.size() > 0, "there's no scc component");
BOOST_ASSERT_MSG(*std::max_element(range.begin(), range.end()) == component.size(),
"scc component ranges are out of bound");
BOOST_ASSERT_MSG(*std::min_element(range.begin(), range.end()) == 0,
"invalid scc component range");
BOOST_ASSERT_MSG(std::is_sorted(std::begin(range), std::end(range)),
"invalid component ranges");
return std::find(std::begin(result_table), std::end(result_table), INVALID_EDGE_WEIGHT) ==
std::end(result_table);
}
std::size_t GetNumberOfComponents() const
bool IsSupportedParameterCombination(const bool fixed_start,
const bool fixed_end,
const bool roundtrip)
{
BOOST_ASSERT_MSG(range.size() > 0, "there's no range");
return range.size() - 1;
}
const std::vector<NodeID> component;
std::vector<std::size_t> range;
};
// takes the number of locations and its duration matrix,
// identifies and splits the graph in its strongly connected components (scc)
// and returns an SCC_Component
SCC_Component SplitUnaccessibleLocations(const std::size_t number_of_locations,
const util::DistTableWrapper<EdgeWeight> &result_table)
{
if (std::find(std::begin(result_table), std::end(result_table), INVALID_EDGE_WEIGHT) ==
std::end(result_table))
{
// whole graph is one scc
std::vector<NodeID> location_ids(number_of_locations);
std::iota(std::begin(location_ids), std::end(location_ids), 0);
std::vector<size_t> range = {0, location_ids.size()};
return SCC_Component(std::move(location_ids), std::move(range));
}
// Run TarjanSCC
auto wrapper = std::make_shared<util::MatrixGraphWrapper<EdgeWeight>>(result_table.GetTable(),
number_of_locations);
auto scc = extractor::TarjanSCC<util::MatrixGraphWrapper<EdgeWeight>>(wrapper);
scc.Run();
const auto number_of_components = scc.GetNumberOfComponents();
std::vector<std::size_t> range_insertion;
std::vector<std::size_t> range;
range_insertion.reserve(number_of_components);
range.reserve(number_of_components);
std::vector<NodeID> components(number_of_locations, 0);
std::size_t prefix = 0;
for (std::size_t j = 0; j < number_of_components; ++j)
{
range_insertion.push_back(prefix);
range.push_back(prefix);
prefix += scc.GetComponentSize(j);
}
// senitel
range.push_back(components.size());
for (std::size_t i = 0; i < number_of_locations; ++i)
{
components[range_insertion[scc.GetComponentID(i)]] = i;
++range_insertion[scc.GetComponentID(i)];
}
return SCC_Component(std::move(components), std::move(range));
if (fixed_start && fixed_end && !roundtrip)
{
return true;
}
else if (roundtrip)
{
return true;
}
else
{
return false;
}
}
// given the node order in which to visit, compute the actual route (with geometry, travel time and
// so on) and return the result
InternalRouteResult
TripPlugin::ComputeRoute(const std::shared_ptr<const datafacade::BaseDataFacade> facade,
const std::vector<PhantomNode> &snapped_phantoms,
const std::vector<NodeID> &trip) const
const std::vector<NodeID> &trip,
const bool roundtrip) const
{
InternalRouteResult min_route;
// given he final trip, compute total duration and return the route and location permutation
// given the final trip, compute total duration and return the route and location permutation
PhantomNodes viapoint;
const auto start = std::begin(trip);
const auto end = std::end(trip);
// computes a roundtrip from the nodes in trip
for (auto it = start; it != end; ++it)
for (auto node = trip.begin(); node < trip.end() - 1; ++node)
{
const auto from_node = *it;
// if from_node is the last node, compute the route from the last to the first location
const auto to_node = std::next(it) != end ? *std::next(it) : *start;
const auto from_node = *node;
const auto to_node = *std::next(node);
viapoint = PhantomNodes{snapped_phantoms[from_node], snapped_phantoms[to_node]};
min_route.segment_end_coordinates.emplace_back(viapoint);
}
// return back to the first node if it is a round trip
if (roundtrip)
{
viapoint = PhantomNodes{snapped_phantoms[trip.back()], snapped_phantoms[trip.front()]};
min_route.segment_end_coordinates.emplace_back(viapoint);
// trip comes out to be something like 0 1 4 3 2 0
BOOST_ASSERT(min_route.segment_end_coordinates.size() == trip.size());
}
else
{
// trip comes out to be something like 0 1 4 3 2, so the sizes don't match
BOOST_ASSERT(min_route.segment_end_coordinates.size() == trip.size() - 1);
}
shortest_path(facade, min_route.segment_end_coordinates, {false}, min_route);
BOOST_ASSERT_MSG(min_route.shortest_path_length < INVALID_EDGE_WEIGHT, "unroutable route");
return min_route;
}
void ManipulateTableForFSE(const std::size_t source_id,
const std::size_t destination_id,
util::DistTableWrapper<EdgeWeight> &result_table)
{
// ****************** Change Table *************************
// The following code manipulates the table and produces the new table for
// Trip with Fixed Start and End (TFSE). In the example the source is a
// and destination is c. The new table forces the roundtrip to start at
// source and end at destination by virtually squashing them together.
// This way the brute force and the farthest insertion algorithms don't
// have to be modified, and instead we can just pass a modified table to
// return a non-roundtrip "optimal" route from a start node to an end node.
// Original Table // New Table
// a b c d e // a b c d e
// a 0 15 36 34 30 // a 0 15 10000 34 30
// b 15 0 25 30 34 // b 10000 0 25 30 34
// c 36 25 0 18 32 // c 0 10000 0 10000 10000
// d 34 30 18 0 15 // d 10000 30 18 0 15
// e 30 34 32 15 0 // e 10000 34 32 15 0
// change parameters.source column
// set any node to source to impossibly high numbers so it will never
// try to use any node->source in the middle of the "optimal path"
for (std::size_t i = 0; i < result_table.GetNumberOfNodes(); i++)
{
if (i == source_id)
continue;
result_table.SetValue(i, source_id, INVALID_EDGE_WEIGHT);
}
// change parameters.destination row
// set destination to anywhere else to impossibly high numbers so it will
// never try to use destination->any node in the middle of the "optimal path"
for (std::size_t i = 0; i < result_table.GetNumberOfNodes(); i++)
{
if (i == destination_id)
continue;
result_table.SetValue(destination_id, i, INVALID_EDGE_WEIGHT);
}
// set destination->source to zero so rountrip treats source and
// destination as one location
result_table.SetValue(destination_id, source_id, 0);
// set source->destination as very high number so algorithm is forced
// to find another path to get to destination
result_table.SetValue(source_id, destination_id, INVALID_EDGE_WEIGHT);
//********* End of changes to table *************************************
}
Status TripPlugin::HandleRequest(const std::shared_ptr<const datafacade::BaseDataFacade> facade,
const api::TripParameters &parameters,
util::json::Object &json_result) const
{
BOOST_ASSERT(parameters.IsValid());
const auto number_of_locations = parameters.coordinates.size();
std::size_t source_id = INVALID_INDEX;
std::size_t destination_id = INVALID_INDEX;
if (parameters.source == api::TripParameters::SourceType::First)
{
source_id = 0;
}
if (parameters.destination == api::TripParameters::DestinationType::Last)
{
BOOST_ASSERT(number_of_locations > 0);
destination_id = number_of_locations - 1;
}
bool fixed_start = (source_id == 0);
bool fixed_end = (destination_id == number_of_locations - 1);
if (!IsSupportedParameterCombination(fixed_start, fixed_end, parameters.roundtrip))
{
return Error("NotImplemented", "This request is not supported", json_result);
}
// enforce maximum number of locations for performance reasons
if (max_locations_trip > 0 &&
static_cast<int>(parameters.coordinates.size()) > max_locations_trip)
if (max_locations_trip > 0 && static_cast<int>(number_of_locations) > max_locations_trip)
{
return Error("TooBig", "Too many trip coordinates", json_result);
}
@ -161,21 +181,27 @@ Status TripPlugin::HandleRequest(const std::shared_ptr<const datafacade::BaseDat
}
auto phantom_node_pairs = GetPhantomNodes(*facade, parameters);
if (phantom_node_pairs.size() != parameters.coordinates.size())
if (phantom_node_pairs.size() != number_of_locations)
{
return Error("NoSegment",
std::string("Could not find a matching segment for coordinate ") +
std::to_string(phantom_node_pairs.size()),
json_result);
}
BOOST_ASSERT(phantom_node_pairs.size() == parameters.coordinates.size());
BOOST_ASSERT(phantom_node_pairs.size() == number_of_locations);
if (fixed_start && fixed_end && (source_id >= parameters.coordinates.size() ||
destination_id >= parameters.coordinates.size()))
{
return Error("InvalidValue", "Invalid source or destination value.", json_result);
}
auto snapped_phantoms = SnapPhantomNodes(phantom_node_pairs);
const auto number_of_locations = snapped_phantoms.size();
BOOST_ASSERT(snapped_phantoms.size() == number_of_locations);
// compute the duration table of all phantom nodes
const auto result_table = util::DistTableWrapper<EdgeWeight>(
auto result_table = util::DistTableWrapper<EdgeWeight>(
duration_table(facade, snapped_phantoms, {}, {}), number_of_locations);
if (result_table.size() == 0)
@ -187,56 +213,49 @@ Status TripPlugin::HandleRequest(const std::shared_ptr<const datafacade::BaseDat
BOOST_ASSERT_MSG(result_table.size() == number_of_locations * number_of_locations,
"Distance Table has wrong size");
// get scc components
SCC_Component scc = SplitUnaccessibleLocations(number_of_locations, result_table);
std::vector<std::vector<NodeID>> trips;
trips.reserve(scc.GetNumberOfComponents());
// run Trip computation for every SCC
for (std::size_t k = 0; k < scc.GetNumberOfComponents(); ++k)
if (!IsStronglyConnectedComponent(result_table))
{
const auto component_size = scc.range[k + 1] - scc.range[k];
return Error("NoTrips", "No trip visiting all destinations possible.", json_result);
}
BOOST_ASSERT_MSG(component_size > 0, "invalid component size");
std::vector<NodeID> scc_route;
auto route_begin = std::begin(scc.component) + scc.range[k];
auto route_end = std::begin(scc.component) + scc.range[k + 1];
if (component_size > 1)
if (fixed_start && fixed_end)
{
ManipulateTableForFSE(source_id, destination_id, result_table);
}
if (component_size < BF_MAX_FEASABLE)
std::vector<NodeID> trip;
trip.reserve(number_of_locations);
// get an optimized order in which the destinations should be visited
if (number_of_locations < BF_MAX_FEASABLE)
{
scc_route =
trip::BruteForceTrip(route_begin, route_end, number_of_locations, result_table);
trip = trip::BruteForceTrip(number_of_locations, result_table);
}
else
{
scc_route = trip::FarthestInsertionTrip(
route_begin, route_end, number_of_locations, result_table);
}
}
else
{
scc_route = std::vector<NodeID>(route_begin, route_end);
trip = trip::FarthestInsertionTrip(number_of_locations, result_table);
}
trips.push_back(std::move(scc_route));
}
if (trips.empty())
// 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)
{
return Error("NoTrips", "Cannot find trips", json_result);
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));
}
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));
}
// compute all round trip routes
std::vector<InternalRouteResult> routes;
routes.reserve(trips.size());
for (const auto &trip : trips)
{
routes.push_back(ComputeRoute(facade, snapped_phantoms, trip));
}
// get the route when visiting all destinations in optimized order
InternalRouteResult route = ComputeRoute(facade, snapped_phantoms, trip, parameters.roundtrip);
// get api response
const std::vector<std::vector<NodeID>> trips = {trip};
const std::vector<InternalRouteResult> routes = {route};
api::TripAPI trip_api{*facade, parameters};
trip_api.MakeResponse(trips, routes, snapped_phantoms, json_result);

View File

@ -15,7 +15,7 @@
BOOST_AUTO_TEST_SUITE(trip)
BOOST_AUTO_TEST_CASE(test_trip_response_for_locations_in_small_component)
BOOST_AUTO_TEST_CASE(test_roundtrip_response_for_locations_in_small_component)
{
const auto args = get_args();
auto osrm = getOSRM(args.at(0));
@ -59,7 +59,7 @@ BOOST_AUTO_TEST_CASE(test_trip_response_for_locations_in_small_component)
}
}
BOOST_AUTO_TEST_CASE(test_trip_response_for_locations_in_big_component)
BOOST_AUTO_TEST_CASE(test_roundtrip_response_for_locations_in_big_component)
{
const auto args = get_args();
auto osrm = getOSRM(args.at(0));
@ -103,7 +103,7 @@ BOOST_AUTO_TEST_CASE(test_trip_response_for_locations_in_big_component)
}
}
BOOST_AUTO_TEST_CASE(test_trip_response_for_locations_across_components)
BOOST_AUTO_TEST_CASE(test_roundtrip_response_for_locations_across_components)
{
const auto args = get_args();
auto osrm = getOSRM(args.at(0));
@ -151,4 +151,279 @@ BOOST_AUTO_TEST_CASE(test_trip_response_for_locations_across_components)
}
}
BOOST_AUTO_TEST_CASE(test_tfse_1)
{
const auto args = get_args();
auto osrm = getOSRM(args.at(0));
using namespace osrm;
const auto locations = get_locations_in_small_component();
TripParameters params;
params.coordinates.push_back(locations.at(0));
params.coordinates.push_back(locations.at(1));
params.coordinates.push_back(locations.at(2));
params.source = TripParameters::SourceType::First;
params.destination = TripParameters::DestinationType::Last;
params.roundtrip = false;
json::Object result;
const auto rc = osrm.Trip(params, result);
BOOST_CHECK(rc == Status::Ok);
const auto code = result.values.at("code").get<json::String>().value;
BOOST_CHECK_EQUAL(code, "Ok");
const auto &waypoints = result.values.at("waypoints").get<json::Array>().values;
BOOST_CHECK_EQUAL(waypoints.size(), params.coordinates.size());
const auto &trips = result.values.at("trips").get<json::Array>().values;
BOOST_CHECK_EQUAL(trips.size(), 1);
for (const auto &waypoint : waypoints)
{
const auto &waypoint_object = waypoint.get<json::Object>();
const auto location = waypoint_object.values.at("location").get<json::Array>().values;
const auto longitude = location[0].get<json::Number>().value;
const auto latitude = location[1].get<json::Number>().value;
BOOST_CHECK(longitude >= -180. && longitude <= 180.);
BOOST_CHECK(latitude >= -90. && latitude <= 90.);
const auto trip = waypoint_object.values.at("trips_index").get<json::Number>().value;
const auto pos = waypoint_object.values.at("waypoint_index").get<json::Number>().value;
BOOST_CHECK(trip >= 0 && trip < trips.size());
BOOST_CHECK(pos >= 0 && pos < waypoints.size());
}
}
BOOST_AUTO_TEST_CASE(test_tfse_2)
{
const auto args = get_args();
auto osrm = getOSRM(args.at(0));
using namespace osrm;
const auto locations = get_locations_in_big_component();
TripParameters params;
params.coordinates.push_back(locations.at(0));
params.coordinates.push_back(locations.at(2));
params.coordinates.push_back(locations.at(1));
params.source = TripParameters::SourceType::First;
params.destination = TripParameters::DestinationType::Last;
params.roundtrip = false;
json::Object result;
const auto rc = osrm.Trip(params, result);
BOOST_CHECK(rc == Status::Ok);
const auto code = result.values.at("code").get<json::String>().value;
BOOST_CHECK_EQUAL(code, "Ok");
const auto &waypoints = result.values.at("waypoints").get<json::Array>().values;
BOOST_CHECK_EQUAL(waypoints.size(), params.coordinates.size());
const auto &trips = result.values.at("trips").get<json::Array>().values;
BOOST_CHECK_EQUAL(trips.size(), 1);
for (const auto &waypoint : waypoints)
{
const auto &waypoint_object = waypoint.get<json::Object>();
const auto location = waypoint_object.values.at("location").get<json::Array>().values;
const auto longitude = location[0].get<json::Number>().value;
const auto latitude = location[1].get<json::Number>().value;
BOOST_CHECK(longitude >= -180. && longitude <= 180.);
BOOST_CHECK(latitude >= -90. && latitude <= 90.);
const auto trip = waypoint_object.values.at("trips_index").get<json::Number>().value;
const auto pos = waypoint_object.values.at("waypoint_index").get<json::Number>().value;
BOOST_CHECK(trip >= 0 && trip < trips.size());
BOOST_CHECK(pos >= 0 && pos < waypoints.size());
}
}
void ResetParams(const Locations &locations, osrm::TripParameters &params)
{
params = osrm::TripParameters();
params.coordinates.push_back(locations.at(0));
params.coordinates.push_back(locations.at(1));
params.coordinates.push_back(locations.at(2));
}
void CheckNotImplemented(const osrm::OSRM &osrm, osrm::TripParameters &params)
{
osrm::json::Object result;
auto rc = osrm.Trip(params, result);
BOOST_REQUIRE(rc == osrm::Status::Error);
auto code = result.values.at("code").get<osrm::json::String>().value;
BOOST_CHECK_EQUAL(code, "NotImplemented");
}
void CheckOk(const osrm::OSRM &osrm, osrm::TripParameters &params)
{
osrm::json::Object result;
auto rc = osrm.Trip(params, result);
BOOST_REQUIRE(rc == osrm::Status::Ok);
auto code = result.values.at("code").get<osrm::json::String>().value;
BOOST_CHECK_EQUAL(code, "Ok");
}
BOOST_AUTO_TEST_CASE(test_tfse_illegal_parameters)
{
const auto args = get_args();
auto osrm = getOSRM(args.at(0));
using namespace osrm;
const auto locations = get_locations_in_big_component();
auto params = osrm::TripParameters();
// one parameter set
ResetParams(locations, params);
params.roundtrip = false;
CheckNotImplemented(osrm, params);
// two parameter set
ResetParams(locations, params);
params.source = TripParameters::SourceType::Any;
params.roundtrip = false;
CheckNotImplemented(osrm, params);
ResetParams(locations, params);
params.source = TripParameters::SourceType::First;
params.roundtrip = false;
CheckNotImplemented(osrm, params);
ResetParams(locations, params);
params.destination = TripParameters::DestinationType::Any;
params.roundtrip = false;
CheckNotImplemented(osrm, params);
ResetParams(locations, params);
params.destination = TripParameters::DestinationType::Last;
params.roundtrip = false;
CheckNotImplemented(osrm, params);
// three parameters set
params.source = TripParameters::SourceType::Any;
params.destination = TripParameters::DestinationType::Any;
params.roundtrip = false;
CheckNotImplemented(osrm, params);
params.source = TripParameters::SourceType::Any;
params.destination = TripParameters::DestinationType::Last;
params.roundtrip = false;
CheckNotImplemented(osrm, params);
params.source = TripParameters::SourceType::First;
params.destination = TripParameters::DestinationType::Any;
params.roundtrip = false;
CheckNotImplemented(osrm, params);
}
BOOST_AUTO_TEST_CASE(test_tfse_legal_parameters)
{
const auto args = get_args();
auto osrm = getOSRM(args.at(0));
using namespace osrm;
const auto locations = get_locations_in_big_component();
json::Object result;
TripParameters params;
// no parameter set
ResetParams(locations, params);
CheckOk(osrm, params);
// one parameter set
ResetParams(locations, params);
params.roundtrip = true;
CheckOk(osrm, params);
ResetParams(locations, params);
params.source = TripParameters::SourceType::First;
CheckOk(osrm, params);
ResetParams(locations, params);
params.source = TripParameters::SourceType::Any;
CheckOk(osrm, params);
ResetParams(locations, params);
params.destination = TripParameters::DestinationType::Any;
CheckOk(osrm, params);
ResetParams(locations, params);
params.destination = TripParameters::DestinationType::Last;
CheckOk(osrm, params);
// two parameter set
ResetParams(locations, params);
params.destination = TripParameters::DestinationType::Last;
params.roundtrip = true;
CheckOk(osrm, params);
ResetParams(locations, params);
params.source = TripParameters::SourceType::First;
params.roundtrip = true;
CheckOk(osrm, params);
ResetParams(locations, params);
params.source = TripParameters::SourceType::First;
params.destination = TripParameters::DestinationType::Any;
CheckOk(osrm, params);
ResetParams(locations, params);
params.source = TripParameters::SourceType::Any;
params.destination = TripParameters::DestinationType::Last;
CheckOk(osrm, params);
ResetParams(locations, params);
params.source = TripParameters::SourceType::First;
params.destination = TripParameters::DestinationType::Last;
CheckOk(osrm, params);
ResetParams(locations, params);
params.source = TripParameters::SourceType::Any;
params.roundtrip = true;
CheckOk(osrm, params);
ResetParams(locations, params);
params.destination = TripParameters::DestinationType::Any;
params.roundtrip = true;
CheckOk(osrm, params);
ResetParams(locations, params);
params.source = TripParameters::SourceType::Any;
params.destination = TripParameters::DestinationType::Any;
CheckOk(osrm, params);
// three parameter set
params.source = TripParameters::SourceType::Any;
params.destination = TripParameters::DestinationType::Any;
params.roundtrip = true;
CheckOk(osrm, params);
params.source = TripParameters::SourceType::First;
params.destination = TripParameters::DestinationType::Last;
params.roundtrip = false;
CheckOk(osrm, params);
params.source = TripParameters::SourceType::Any;
params.destination = TripParameters::DestinationType::Last;
params.roundtrip = true;
CheckOk(osrm, params);
params.source = TripParameters::SourceType::First;
params.destination = TripParameters::DestinationType::Any;
params.roundtrip = true;
CheckOk(osrm, params);
params.source = TripParameters::SourceType::First;
params.destination = TripParameters::DestinationType::Last;
params.roundtrip = true;
CheckOk(osrm, params);
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -495,9 +495,43 @@ BOOST_AUTO_TEST_CASE(valid_trip_urls)
reference_1.coordinates = coords_1;
auto result_1 = parseParameters<TripParameters>("1,2;3,4");
BOOST_CHECK(result_1);
CHECK_EQUAL_RANGE(reference_1.bearings, result_1->bearings);
CHECK_EQUAL_RANGE(reference_1.radiuses, result_1->radiuses);
CHECK_EQUAL_RANGE(reference_1.coordinates, result_1->coordinates);
TripParameters reference_2{};
reference_2.coordinates = coords_1;
reference_2.source = TripParameters::SourceType::First;
reference_2.destination = TripParameters::DestinationType::Last;
auto result_2 = parseParameters<TripParameters>("1,2;3,4?source=first&destination=last");
BOOST_CHECK(result_2);
CHECK_EQUAL_RANGE(reference_2.radiuses, result_2->radiuses);
CHECK_EQUAL_RANGE(reference_2.coordinates, result_2->coordinates);
// check supported source/destination/rountrip combinations
auto param_fse_r =
parseParameters<TripParameters>("1,2;3,4?source=first&destination=last&roundtrip=true");
BOOST_CHECK(param_fse_r->IsValid());
auto param_fse_nr_ =
parseParameters<TripParameters>("1,2;3,4?source=first&destination=last&roundtrip=false");
BOOST_CHECK(param_fse_nr_->IsValid());
auto param_fs_r = parseParameters<TripParameters>("1,2;3,4?source=first&roundtrip=true");
BOOST_CHECK(param_fs_r->IsValid());
auto param_fs_nr = parseParameters<TripParameters>("1,2;3,4?source=first&roundtrip=false");
BOOST_CHECK(param_fs_nr->IsValid());
auto param_fe_r = parseParameters<TripParameters>("1,2;3,4?destination=last&roundtrip=true");
BOOST_CHECK(param_fe_r->IsValid());
auto param_fe_nr = parseParameters<TripParameters>("1,2;3,4?destination=last&roundtrip=false");
BOOST_CHECK(param_fe_nr->IsValid());
auto param_r = parseParameters<TripParameters>("1,2;3,4?roundtrip=true");
BOOST_CHECK(param_r->IsValid());
auto param_nr = parseParameters<TripParameters>("1,2;3,4?roundtrip=false");
BOOST_CHECK(param_nr->IsValid());
auto param_fail_1 =
testInvalidOptions<TripParameters>("1,2;3,4?source=blubb&destination=random");
BOOST_CHECK_EQUAL(param_fail_1, 15UL);
auto param_fail_2 = testInvalidOptions<TripParameters>("1,2;3,4?source=first&destination=nah");
BOOST_CHECK_EQUAL(param_fail_2, 33UL);
}
BOOST_AUTO_TEST_SUITE_END()