Trip with Fixed Start and End points (TFSE) (#3408)
* fixed start and end trip feature to trip service
This commit is contained in:
parent
3e2db47cc8
commit
2218658969
@ -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
|
||||
|
54
docs/http.md
54
docs/http.md
@ -175,7 +175,7 @@ In addition to the [general options](#general-options) the following options are
|
||||
|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.|
|
||||
|continue\_straight |`default` (default), `true`, `false` |Forces the route to keep going straight at waypoints constraining uturns there even if it would be faster. Default value depends on the profile. |
|
||||
|continue\_straight |`default` (default), `true`, `false` |Forces the route to keep going straight at waypoints constraining uturns there even if it would be faster. Default value depends on the profile. |
|
||||
|
||||
\* Please note that even if an alternative route is requested, a result cannot be guaranteed.
|
||||
|
||||
@ -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.
|
||||
|
||||
|
@ -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');
|
||||
|
@ -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
|
||||
@ -62,7 +152,7 @@ Feature: Basic trip planning
|
||||
j i h g
|
||||
|
||||
q m n
|
||||
p o
|
||||
p o
|
||||
"""
|
||||
|
||||
And the ways
|
||||
@ -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 | source | destination | roundtrip | status | message |
|
||||
| a,b,c,d | first | last | false | NoTrips | No trip visiting all destinations possible. |
|
||||
|
||||
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 | trips |
|
||||
| a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p | defghijklabcd,mnopm |
|
||||
| waypoints | source | destination |roundtrip | trips | durations | distance |
|
||||
| a,b,d,e,c | first | last | false | abedc | 8.200000000000001 | 81.6 |
|
||||
|
||||
# Test single node in each component #1850
|
||||
Scenario: Testbot - Trip planning with less than 10 nodes
|
||||
|
||||
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 |
|
||||
|
||||
|
@ -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(); }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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_)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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_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
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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_); }
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
std::size_t GetNumberOfComponents() const
|
||||
{
|
||||
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));
|
||||
return std::find(std::begin(result_table), std::end(result_table), INVALID_EDGE_WEIGHT) ==
|
||||
std::end(result_table);
|
||||
}
|
||||
|
||||
bool IsSupportedParameterCombination(const bool fixed_start,
|
||||
const bool fixed_end,
|
||||
const bool roundtrip)
|
||||
{
|
||||
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);
|
||||
}
|
||||
BOOST_ASSERT(min_route.segment_end_coordinates.size() == trip.size());
|
||||
|
||||
// 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 ¶meters,
|
||||
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];
|
||||
|
||||
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 (component_size < BF_MAX_FEASABLE)
|
||||
{
|
||||
scc_route =
|
||||
trip::BruteForceTrip(route_begin, route_end, 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);
|
||||
}
|
||||
|
||||
trips.push_back(std::move(scc_route));
|
||||
}
|
||||
if (trips.empty())
|
||||
{
|
||||
return Error("NoTrips", "Cannot find trips", json_result);
|
||||
return Error("NoTrips", "No trip visiting all destinations possible.", json_result);
|
||||
}
|
||||
|
||||
// compute all round trip routes
|
||||
std::vector<InternalRouteResult> routes;
|
||||
routes.reserve(trips.size());
|
||||
for (const auto &trip : trips)
|
||||
if (fixed_start && fixed_end)
|
||||
{
|
||||
routes.push_back(ComputeRoute(facade, snapped_phantoms, trip));
|
||||
ManipulateTableForFSE(source_id, destination_id, result_table);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
trip = trip::BruteForceTrip(number_of_locations, result_table);
|
||||
}
|
||||
else
|
||||
{
|
||||
trip = trip::FarthestInsertionTrip(number_of_locations, result_table);
|
||||
}
|
||||
|
||||
// rotate result such that roundtrip starts at node with index 0
|
||||
// thist first if covers scenarios: !fixed_end || fixed_start || (fixed_start && fixed_end)
|
||||
if (!fixed_end || fixed_start)
|
||||
{
|
||||
auto desired_start_index = std::find(std::begin(trip), std::end(trip), 0);
|
||||
BOOST_ASSERT(desired_start_index != std::end(trip));
|
||||
std::rotate(std::begin(trip), desired_start_index, std::end(trip));
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
|
@ -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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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()
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user