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 - libOSRM now creates an own watcher thread then used in shared memory mode to listen for data updates
- Tools: - Tools:
- Added osrm-extract-conditionals tool for checking conditional values in OSM data - 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 # 5.5.1
- Changes from 5.5.0 - Changes from 5.5.0

View File

@ -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. | |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) | |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.| |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. \* 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 ### Trip service
The trip plugin solves the Traveling Salesman Problem using a greedy heuristic (farthest-insertion algorithm). 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 is only an approximation. The returned path does not have to be the fastest path. As TSP is NP-hard it only returns 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) Note that all input coordinates have to be connected for the trip service to work.
multiple trips for each connected component are returned.
```endpoint ```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: In addition to the [general options](#general-options) the following options are supported for this service:
|Option |Values |Description | |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 | |steps |`true`, `false` (default) |Return route instructions for each trip |
|annotations |`true`, `false` (default) |Returns additional metadata for each coordinate along the route geometry. | |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) | |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.| |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: - `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. - `trips_index`: Index to `trips` of the sub-trip the point was matched to.
- `waypoint_index`: Index of the point in the trip. - `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 | | 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. All other properties might be undefined.

View File

@ -24,7 +24,6 @@ module.exports = function () {
} else if (row[k]) { } else if (row[k]) {
params[match[1]] = [row[k]]; params[match[1]] = [row[k]];
} }
got[k] = row[k]; got[k] = row[k];
} }
} }
@ -35,11 +34,11 @@ module.exports = function () {
} }
if (headers.has('status')) { if (headers.has('status')) {
got.status = json.status.toString(); got.status = json.code;
} }
if (headers.has('message')) { if (headers.has('message')) {
got.message = json.status_message; got.message = json.message;
} }
if (headers.has('geometry')) { if (headers.has('geometry')) {
@ -59,6 +58,7 @@ module.exports = function () {
var subTrips; var subTrips;
var trip_durations; var trip_durations;
var trip_distance;
if (res.statusCode === 200) { if (res.statusCode === 200) {
if (headers.has('trips')) { if (headers.has('trips')) {
subTrips = json.trips.filter(t => !!t).map(t => t.legs).map(tl => Array.prototype.concat.apply([], tl.map((sl, i) => { 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)); 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, var ok = true,
encodedResult = '', encodedResult = '',
extendedTarget = ''; extendedTarget = '';
row.trips.split(',').forEach((sub, si) => { if (json.trips) row.trips.split(',').forEach((sub, si) => {
if (si >= subTrips.length) { if (si >= subTrips.length) {
ok = false; ok = false;
} else { } else {
@ -105,10 +111,10 @@ module.exports = function () {
got.via_points = row.via_points; got.via_points = row.via_points;
} else { } else {
got.trips = encodedResult; got.trips = encodedResult;
got.trips = extendedTarget;
} }
got.durations = trip_durations; got.durations = trip_durations;
got.distance = trip_distance;
for (var key in row) { for (var key in row) {
if (this.FuzzyMatch.match(got[key], row[key])) { if (this.FuzzyMatch.match(got[key], row[key])) {
@ -144,6 +150,19 @@ module.exports = function () {
waypoints.push(node); waypoints.push(node);
}); });
got = { waypoints: row.waypoints }; 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); this.requestTrip(waypoints, params, afterRequest);
} else { } else {
throw new Error('*** no waypoints'); throw new Error('*** no waypoints');

View File

@ -5,7 +5,25 @@ Feature: Basic trip planning
Given the profile "testbot" Given the profile "testbot"
Given a grid size of 10 meters 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 Given the node map
""" """
a b a b
@ -24,7 +42,7 @@ Feature: Basic trip planning
| a,b,c,d | abcda | 7.6 | | a,b,c,d | abcda | 7.6 |
| d,b,c,a | dbcad | 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 Given the node map
""" """
a b c d a b c d
@ -37,7 +55,6 @@ Feature: Basic trip planning
| nodes | | nodes |
| ab | | ab |
| bc | | bc |
| cb |
| de | | de |
| ef | | ef |
| fg | | fg |
@ -48,12 +65,85 @@ Feature: Basic trip planning
| kl | | kl |
| la | | 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 When I plan a trip I should get
| waypoints | trips | | 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 Given the node map
""" """
a b c d a b c d
@ -62,7 +152,7 @@ Feature: Basic trip planning
j i h g j i h g
q m n q m n
p o p o
""" """
And the ways And the ways
@ -85,13 +175,106 @@ Feature: Basic trip planning
| pq | | pq |
| qm | | 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 When I plan a trip I should get
| waypoints | trips | | waypoints | source | destination |roundtrip | trips | durations | distance |
| a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p | defghijklabcd,mnopm | | 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 Given the node map
""" """
a 1 b a 1 b
@ -105,10 +288,10 @@ Feature: Basic trip planning
| cd | | cd |
When I plan a trip I should get When I plan a trip I should get
| waypoints | trips | | waypoints | status | message |
| 1,2 | | | 1,2 | NoTrips | No trip visiting all destinations possible. |
Scenario: Testbot - Repeated Coordinate Scenario: Testbot - Trip: Repeated Coordinate
Given the node map Given the node map
""" """
a b 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 | | | 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 Given the query options
| geometries | geojson | | 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 | | 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 | | 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 Given the query options
| geometries | polyline | | 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 | | 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 | | 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 Given the query options
| geometries | polyline6 | | geometries | polyline6 |
@ -187,4 +370,4 @@ Feature: Basic trip planning
When I plan a trip I should get When I plan a trip I should get
| waypoints | trips | durations | geometry | | waypoints | trips | durations | geometry |
| 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 | | 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 | | 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 |

View File

@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "engine/api/route_parameters.hpp" #include "engine/api/route_parameters.hpp"
#include <boost/optional.hpp>
#include <vector> #include <vector>
namespace osrm namespace osrm
@ -47,7 +48,33 @@ namespace api
*/ */
struct TripParameters : public RouteParameters 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, InternalRouteResult ComputeRoute(const std::shared_ptr<const datafacade::BaseDataFacade> facade,
const std::vector<PhantomNode> &phantom_node_list, const std::vector<PhantomNode> &phantom_node_list,
const std::vector<NodeID> &trip) const; const std::vector<NodeID> &trip,
const bool roundtrip) const;
public: public:
explicit TripPlugin(const int max_locations_trip_) explicit TripPlugin(const int max_locations_trip_)

View File

@ -11,6 +11,7 @@
#include <cstdlib> #include <cstdlib>
#include <iterator> #include <iterator>
#include <limits> #include <limits>
#include <numeric>
#include <string> #include <string>
#include <vector> #include <vector>
@ -25,62 +26,76 @@ namespace trip
EdgeWeight ReturnDistance(const util::DistTableWrapper<EdgeWeight> &dist_table, EdgeWeight ReturnDistance(const util::DistTableWrapper<EdgeWeight> &dist_table,
const std::vector<NodeID> &location_order, const std::vector<NodeID> &location_order,
const EdgeWeight min_route_dist, const EdgeWeight min_route_dist,
const std::size_t component_size) const std::size_t number_of_locations)
{ {
EdgeWeight route_dist = 0; EdgeWeight route_dist = 0;
std::size_t i = 0; std::size_t current_index = 0;
while (i < location_order.size() && (route_dist < min_route_dist)) 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_EDGE_WEIGHT,
"invalid route found"); "invalid route found");
++i; ++current_index;
} }
return route_dist; return route_dist;
} }
// computes the route by computing all permutations and selecting the shortest // computes the route by computing all permutations and selecting the shortest
template <typename NodeIDIterator> std::vector<NodeID> BruteForceTrip(const std::size_t number_of_locations,
std::vector<NodeID> BruteForceTrip(const NodeIDIterator start,
const NodeIDIterator end,
const std::size_t number_of_locations,
const util::DistTableWrapper<EdgeWeight> &dist_table) const util::DistTableWrapper<EdgeWeight> &dist_table)
{ {
(void)number_of_locations; // unused // set initial order in which nodes are visited to 0, 1, 2, 3, ...
std::vector<NodeID> node_order(number_of_locations);
const auto component_size = std::distance(start, end); std::iota(std::begin(node_order), std::end(node_order), 0);
std::vector<NodeID> route = node_order;
std::vector<NodeID> perm(start, end);
std::vector<NodeID> route = perm;
EdgeWeight min_route_dist = INVALID_EDGE_WEIGHT; EdgeWeight min_route_dist = INVALID_EDGE_WEIGHT;
// check length of all possible permutation of the component ids // check length of all possible permutation of the component ids
BOOST_ASSERT_MSG(node_order.size() > 0, "no order permutation given");
BOOST_ASSERT_MSG(perm.size() > 0, "no permutation given"); BOOST_ASSERT_MSG(*(std::max_element(std::begin(node_order), std::end(node_order))) <
BOOST_ASSERT_MSG(*(std::max_element(std::begin(perm), std::end(perm))) < number_of_locations, number_of_locations,
"invalid node id");
BOOST_ASSERT_MSG(*(std::min_element(std::begin(node_order), std::end(node_order))) >= 0,
"invalid node id"); "invalid node id");
BOOST_ASSERT_MSG(*(std::min_element(std::begin(perm), std::end(perm))) >= 0, "invalid node id");
do 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 // 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 // In case we really sum up to invalid edge weight for all permutations, keeping the very
// first one is fine too. // first one is fine too.
if (new_distance < min_route_dist) if (new_distance < min_route_dist)
{ {
min_route_dist = new_distance; 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; return route;
} }
}
} } // namespace trip
} } // namespace engine
} // namespace osrm
#endif // TRIP_BRUTE_FORCE_HPP #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 dist_to = dist_table(new_loc, *to_node);
const auto trip_dist = dist_from + dist_to - dist_table(*from_node, *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"); // If the edge_weight is very large (INVALID_EDGE_WEIGHT) then the algorithm will not choose
BOOST_ASSERT_MSG(dist_to != INVALID_EDGE_WEIGHT, "distance has invalid edge weight"); // 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: // This is not neccessarily true:
// Lets say you have an edge (u, v) with duration 100. If you place a coordinate exactly in // 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. // 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); 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 // 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, 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 util::DistTableWrapper<EdgeWeight> &dist_table,
const NodeID &start1, const NodeID &start1,
const NodeID &start2) const NodeID &start2)
{ {
BOOST_ASSERT_MSG(number_of_locations >= component_size, BOOST_ASSERT_MSG(number_of_locations * number_of_locations == dist_table.size(),
"component size bigger than total number of locations"); "number_of_locations and dist_table size do not match");
std::vector<NodeID> route; std::vector<NodeID> route;
route.reserve(number_of_locations); 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(start1);
route.push_back(start2); route.push_back(start2);
// add all other nodes missing (two nodes are already in the initial start trip) // two nodes are already in the initial start trip, so we need to add all other nodes
for (std::size_t j = 2; j < component_size; ++j) for (std::size_t added_nodes = 2; added_nodes < number_of_locations; ++added_nodes)
{ {
auto farthest_distance = std::numeric_limits<int>::min(); auto farthest_distance = std::numeric_limits<int>::min();
auto next_node = -1; auto next_node = -1;
NodeIDIter next_insert_point; NodeIDIter next_insert_point;
// find unvisited loc i that is the farthest away from all other visited locs // find unvisited node that is the farthest away from all other visited locs
for (auto i = start; i != end; ++i) for (std::size_t id = 0; id < number_of_locations; ++id)
{ {
// find the shortest distance from i to all visited nodes // find the shortest distance from i to all visited nodes
if (!visited[*i]) if (!visited[id])
{ {
const auto insert_candidate = 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, BOOST_ASSERT_MSG(insert_candidate.first != INVALID_EDGE_WEIGHT,
"shortest round trip is invalid"); "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) if (insert_candidate.first > farthest_distance)
{ {
farthest_distance = insert_candidate.first; farthest_distance = insert_candidate.first;
next_node = *i; next_node = id;
next_insert_point = insert_candidate.second; next_insert_point = insert_candidate.second;
} }
} }
@ -136,10 +134,7 @@ std::vector<NodeID> FindRoute(const std::size_t &number_of_locations,
return route; return route;
} }
template <typename NodeIDIterator> std::vector<NodeID> FarthestInsertionTrip(const std::size_t number_of_locations,
std::vector<NodeID> FarthestInsertionTrip(const NodeIDIterator &start,
const NodeIDIterator &end,
const std::size_t number_of_locations,
const util::DistTableWrapper<EdgeWeight> &dist_table) 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. // Guard against dist_table being empty therefore max_element returning the end iterator.
BOOST_ASSERT(dist_table.size() > 0); BOOST_ASSERT(dist_table.size() > 0);
const auto component_size = std::distance(start, end); BOOST_ASSERT_MSG(number_of_locations * number_of_locations == dist_table.size(),
BOOST_ASSERT(component_size >= 0); "number_of_locations and dist_table size do not match");
auto max_from = -1; // find the pair of location with the biggest distance and make the pair the initial start
auto max_to = -1; // 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.
if (static_cast<std::size_t>(component_size) == number_of_locations) const auto index_of_farthest_distance = std::distance(
{ std::begin(dist_table), std::max_element(std::begin(dist_table) + 1, std::end(dist_table)));
// find the pair of location with the biggest distance and make the pair the initial start // distance table is a nxn matrix with the distance(u,v) in column u and row v
// trip. Skipping over the very first element (0,0), we make sure not to end up with the // but the distance table is stored in an 1D array of distances
// same start/end in the special case where all entries are the same. // to get the actual (u,v), get the row by dividing and the column by computing modulo n
const auto index = NodeID max_from = index_of_farthest_distance / number_of_locations;
std::distance(std::begin(dist_table), NodeID max_to = index_of_farthest_distance % number_of_locations;
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;
}
}
}
}
BOOST_ASSERT(max_from >= 0); BOOST_ASSERT(max_from >= 0);
BOOST_ASSERT(max_to >= 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_from) < number_of_locations, "start node");
BOOST_ASSERT_MSG(static_cast<std::size_t>(max_to) < 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 #endif // TRIP_FARTHEST_INSERTION_HPP

View File

@ -4,6 +4,7 @@
#include "server/api/route_parameters_grammar.hpp" #include "server/api/route_parameters_grammar.hpp"
#include "engine/api/trip_parameters.hpp" #include "engine/api/trip_parameters.hpp"
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/qi.hpp>
namespace osrm namespace osrm
@ -15,6 +16,7 @@ namespace api
namespace namespace
{ {
namespace ph = boost::phoenix;
namespace qi = boost::spirit::qi; namespace qi = boost::spirit::qi;
} }
@ -26,12 +28,37 @@ struct TripParametersGrammar final : public RouteParametersGrammar<Iterator, Sig
TripParametersGrammar() : BaseGrammar(root_rule) 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") > 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: 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::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]; 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_); } ConstIterator begin() const { return std::begin(table_); }
Iterator begin() { 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 // This Wrapper provides all methods that are needed for extractor::TarjanSCC, when the graph is
// given in a // given in a matrix representation (e.g. as output from a distance table call)
// matrix representation (e.g. as output from a distance table call)
template <typename T> class MatrixGraphWrapper template <typename T> class MatrixGraphWrapper
{ {

View File

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

View File

@ -1,7 +1,5 @@
#include "engine/plugins/trip.hpp" #include "engine/plugins/trip.hpp"
#include "extractor/tarjan_scc.hpp"
#include "engine/api/trip_api.hpp" #include "engine/api/trip_api.hpp"
#include "engine/api/trip_parameters.hpp" #include "engine/api/trip_parameters.hpp"
#include "engine/trip/trip_brute_force.hpp" #include "engine/trip/trip_brute_force.hpp"
@ -16,6 +14,7 @@
#include <algorithm> #include <algorithm>
#include <cstdlib> #include <cstdlib>
#include <iterator> #include <iterator>
#include <limits>
#include <memory> #include <memory>
#include <string> #include <string>
#include <utility> #include <utility>
@ -28,129 +27,150 @@ namespace engine
namespace plugins namespace plugins
{ {
// Object to hold all strongly connected components (scc) of a graph bool IsStronglyConnectedComponent(const util::DistTableWrapper<EdgeWeight> &result_table)
// 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
{ {
// in_component: all NodeIDs sorted by component ID return std::find(std::begin(result_table), std::end(result_table), INVALID_EDGE_WEIGHT) ==
// in_range: index where a new component starts std::end(result_table);
//
// 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));
} }
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 InternalRouteResult
TripPlugin::ComputeRoute(const std::shared_ptr<const datafacade::BaseDataFacade> facade, TripPlugin::ComputeRoute(const std::shared_ptr<const datafacade::BaseDataFacade> facade,
const std::vector<PhantomNode> &snapped_phantoms, const std::vector<PhantomNode> &snapped_phantoms,
const std::vector<NodeID> &trip) const const std::vector<NodeID> &trip,
const bool roundtrip) const
{ {
InternalRouteResult min_route; 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; PhantomNodes viapoint;
const auto start = std::begin(trip);
const auto end = std::end(trip);
// computes a roundtrip from the nodes in 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; const auto from_node = *node;
// if from_node is the last node, compute the route from the last to the first location const auto to_node = *std::next(node);
const auto to_node = std::next(it) != end ? *std::next(it) : *start;
viapoint = PhantomNodes{snapped_phantoms[from_node], snapped_phantoms[to_node]}; viapoint = PhantomNodes{snapped_phantoms[from_node], snapped_phantoms[to_node]};
min_route.segment_end_coordinates.emplace_back(viapoint); 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); shortest_path(facade, min_route.segment_end_coordinates, {false}, min_route);
BOOST_ASSERT_MSG(min_route.shortest_path_length < INVALID_EDGE_WEIGHT, "unroutable route"); BOOST_ASSERT_MSG(min_route.shortest_path_length < INVALID_EDGE_WEIGHT, "unroutable route");
return min_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, Status TripPlugin::HandleRequest(const std::shared_ptr<const datafacade::BaseDataFacade> facade,
const api::TripParameters &parameters, const api::TripParameters &parameters,
util::json::Object &json_result) const util::json::Object &json_result) const
{ {
BOOST_ASSERT(parameters.IsValid()); 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 // enforce maximum number of locations for performance reasons
if (max_locations_trip > 0 && if (max_locations_trip > 0 && static_cast<int>(number_of_locations) > max_locations_trip)
static_cast<int>(parameters.coordinates.size()) > max_locations_trip)
{ {
return Error("TooBig", "Too many trip coordinates", json_result); 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); 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", return Error("NoSegment",
std::string("Could not find a matching segment for coordinate ") + std::string("Could not find a matching segment for coordinate ") +
std::to_string(phantom_node_pairs.size()), std::to_string(phantom_node_pairs.size()),
json_result); 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); 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 // 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); duration_table(facade, snapped_phantoms, {}, {}), number_of_locations);
if (result_table.size() == 0) 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, BOOST_ASSERT_MSG(result_table.size() == number_of_locations * number_of_locations,
"Distance Table has wrong size"); "Distance Table has wrong size");
// get scc components if (!IsStronglyConnectedComponent(result_table))
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)
{ {
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 (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);
} }
// compute all round trip routes if (fixed_start && fixed_end)
std::vector<InternalRouteResult> routes;
routes.reserve(trips.size());
for (const auto &trip : trips)
{ {
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}; api::TripAPI trip_api{*facade, parameters};
trip_api.MakeResponse(trips, routes, snapped_phantoms, json_result); trip_api.MakeResponse(trips, routes, snapped_phantoms, json_result);
@ -244,4 +263,4 @@ Status TripPlugin::HandleRequest(const std::shared_ptr<const datafacade::BaseDat
} }
} }
} }
} }

View File

@ -15,7 +15,7 @@
BOOST_AUTO_TEST_SUITE(trip) 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(); const auto args = get_args();
auto osrm = getOSRM(args.at(0)); 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(); const auto args = get_args();
auto osrm = getOSRM(args.at(0)); 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(); const auto args = get_args();
auto osrm = getOSRM(args.at(0)); 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() BOOST_AUTO_TEST_SUITE_END()

View File

@ -495,9 +495,43 @@ BOOST_AUTO_TEST_CASE(valid_trip_urls)
reference_1.coordinates = coords_1; reference_1.coordinates = coords_1;
auto result_1 = parseParameters<TripParameters>("1,2;3,4"); auto result_1 = parseParameters<TripParameters>("1,2;3,4");
BOOST_CHECK(result_1); 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.radiuses, result_1->radiuses);
CHECK_EQUAL_RANGE(reference_1.coordinates, result_1->coordinates); 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() BOOST_AUTO_TEST_SUITE_END()