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 | ||||
|  | ||||
							
								
								
									
										52
									
								
								docs/http.md
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								docs/http.md
									
									
									
									
									
								
							| @ -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 | ||||
| @ -85,13 +175,106 @@ Feature: Basic trip planning | ||||
|             | pq    | | ||||
|             | qm    | | ||||
| 
 | ||||
|         When I plan a trip I should get | ||||
|             | waypoints                       | status         | message                                      | | ||||
|             | a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p | NoTrips        | No trip visiting all destinations possible.  | | ||||
| 
 | ||||
| # Test TFSE | ||||
|     Scenario: Testbot - Trip: TFSE with errors | ||||
|         Given the node map | ||||
|             """ | ||||
|             a b | ||||
| 
 | ||||
|             d c | ||||
|             """ | ||||
| 
 | ||||
|          And the ways | ||||
|             | nodes | | ||||
|             | ab    | | ||||
|             | dc    | | ||||
| 
 | ||||
|          When I plan a trip I should get | ||||
|             | waypoints                       | trips               | | ||||
|             | a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p | defghijklabcd,mnopm | | ||||
|             |  waypoints    | source  | destination | roundtrip | status           | message                                       | | ||||
|             |  a,b,c,d      | first   | last        | false     | NoTrips          | No trip visiting all destinations possible.   | | ||||
| 
 | ||||
|     # Test single node in each component #1850 | ||||
|     Scenario: Testbot - Trip planning with less than 10 nodes | ||||
|     Scenario: Testbot - Trip: FSE with waypoints (less than 10) | ||||
|         Given the node map | ||||
|             """ | ||||
|             a  b | ||||
| 
 | ||||
|                   c | ||||
|             e  d | ||||
|             """ | ||||
| 
 | ||||
|         And the ways | ||||
|             | nodes | | ||||
|             | ab    | | ||||
|             | ac    | | ||||
|             | ad    | | ||||
|             | ae    | | ||||
|             | bc    | | ||||
|             | bd    | | ||||
|             | be    | | ||||
|             | cd    | | ||||
|             | ce    | | ||||
|             | de    | | ||||
| 
 | ||||
|         When I plan a trip I should get | ||||
|             |  waypoints  | source | destination |roundtrip | trips  | durations         | distance               | | ||||
|             |  a,b,d,e,c  | first  | last        | false    | abedc  | 8.200000000000001 | 81.6                   | | ||||
| 
 | ||||
| 
 | ||||
|     Scenario: Testbot - Trip: FSE with waypoints (more than 10) | ||||
|         Given the node map | ||||
|             """ | ||||
|             a b c d e f g h i j k | ||||
|             """ | ||||
| 
 | ||||
|         And the ways | ||||
|             | nodes | | ||||
|             | ab    | | ||||
|             | bc    | | ||||
|             | cd    | | ||||
|             | de    | | ||||
|             | ef    | | ||||
|             | fg    | | ||||
|             | gh    | | ||||
|             | hi    | | ||||
|             | ij    | | ||||
|             | jk    | | ||||
| 
 | ||||
|         When I plan a trip I should get | ||||
|             |  waypoints              | source | destination | roundtrip |  trips       | durations  | distance  | | ||||
|             |  a,b,c,d,e,h,i,j,k,g,f  | first  | last        | false     | abcdeghijkf  | 15         | 149.8     | | ||||
| 
 | ||||
|     Scenario: Testbot - Trip: FSE roundtrip with waypoints (less than 10) | ||||
|         Given the node map | ||||
|             """ | ||||
|             a  b | ||||
| 
 | ||||
|                   c | ||||
|             e  d | ||||
|             """ | ||||
| 
 | ||||
|         And the ways | ||||
|             | nodes | | ||||
|             | ab    | | ||||
|             | ac    | | ||||
|             | ad    | | ||||
|             | ae    | | ||||
|             | bc    | | ||||
|             | bd    | | ||||
|             | be    | | ||||
|             | cd    | | ||||
|             | ce    | | ||||
|             | de    | | ||||
| 
 | ||||
|         When I plan a trip I should get | ||||
|             |  waypoints  | source | destination | roundtrip | trips   | | ||||
|             |  a,b,d,e,c  | first  | last        | true      | abedca  | | ||||
| 
 | ||||
| 
 | ||||
|     Scenario: Testbot - Trip: midway points in isoldated roads should return no trips | ||||
|         Given the node map | ||||
|             """ | ||||
|             a 1 b | ||||
| @ -105,10 +288,10 @@ Feature: Basic trip planning | ||||
|             | cd    | | ||||
| 
 | ||||
|         When I plan a trip I should get | ||||
|             | waypoints | trips | | ||||
|             | 1,2       |       | | ||||
|             | waypoints | status  | message                                     | | ||||
|             | 1,2       | NoTrips | No trip visiting all destinations possible. | | ||||
| 
 | ||||
|     Scenario: Testbot - Repeated Coordinate | ||||
|     Scenario: Testbot - Trip: Repeated Coordinate | ||||
|         Given the node map | ||||
|             """ | ||||
|             a   b | ||||
| @ -123,7 +306,7 @@ Feature: Basic trip planning | ||||
|             | a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a,a |       | | ||||
| 
 | ||||
| 
 | ||||
|     Scenario: Testbot - Trip with geometry details of geojson | ||||
|     Scenario: Testbot - Trip: geometry details of geojson | ||||
|         Given the query options | ||||
|             | geometries | geojson  | | ||||
| 
 | ||||
| @ -145,7 +328,7 @@ Feature: Basic trip planning | ||||
|             | a,b,c,d   | abcda  | 7.6       | 1,1,1.00009,1,1,0.99991,1.00009,1,1,1,1.00009,0.99991,1,1              | | ||||
|             | d,b,c,a   | dbcad  | 7.6       | 1.00009,0.99991,1,1,1.00009,1,1,0.99991,1.00009,1,1,1,1.00009,0.99991  | | ||||
| 
 | ||||
|     Scenario: Testbot - Trip with geometry details of polyline | ||||
|     Scenario: Testbot - Trip: geometry details of polyline | ||||
|         Given the query options | ||||
|             | geometries | polyline  | | ||||
| 
 | ||||
| @ -167,7 +350,7 @@ Feature: Basic trip planning | ||||
|             | a,b,c,d   | abcda  | 7.6       | 1,1,1,1.00009,0.99991,1,1,1.00009,1,1,0.99991,1.00009,1,1             | | ||||
|             | d,b,c,a   | dbcad  | 7.6       | 0.99991,1.00009,1,1,1,1.00009,0.99991,1,1,1.00009,1,1,0.99991,1.00009 | | ||||
| 
 | ||||
|     Scenario: Testbot - Trip with geometry details of polyline6 | ||||
|     Scenario: Testbot - Trip: geometry details of polyline6 | ||||
|         Given the query options | ||||
|             | geometries | polyline6  | | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     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); | ||||
|     } | ||||
| 
 | ||||
|     // 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]; | ||||
|         return Error("NoTrips", "No trip visiting all destinations possible.", json_result); | ||||
|     } | ||||
| 
 | ||||
|         BOOST_ASSERT_MSG(component_size > 0, "invalid component size"); | ||||
| 
 | ||||
|         std::vector<NodeID> scc_route; | ||||
|         auto route_begin = std::begin(scc.component) + scc.range[k]; | ||||
|         auto route_end = std::begin(scc.component) + scc.range[k + 1]; | ||||
| 
 | ||||
|         if (component_size > 1) | ||||
|     if (fixed_start && fixed_end) | ||||
|     { | ||||
|         ManipulateTableForFSE(source_id, destination_id, result_table); | ||||
|     } | ||||
| 
 | ||||
|             if (component_size < BF_MAX_FEASABLE) | ||||
|     std::vector<NodeID> trip; | ||||
|     trip.reserve(number_of_locations); | ||||
|     // get an optimized order in which the destinations should be visited
 | ||||
|     if (number_of_locations < BF_MAX_FEASABLE) | ||||
|     { | ||||
|                 scc_route = | ||||
|                     trip::BruteForceTrip(route_begin, route_end, number_of_locations, result_table); | ||||
|         trip = trip::BruteForceTrip(number_of_locations, result_table); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|                 scc_route = trip::FarthestInsertionTrip( | ||||
|                     route_begin, route_end, number_of_locations, result_table); | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             scc_route = std::vector<NodeID>(route_begin, route_end); | ||||
|         trip = trip::FarthestInsertionTrip(number_of_locations, result_table); | ||||
|     } | ||||
| 
 | ||||
|         trips.push_back(std::move(scc_route)); | ||||
|     } | ||||
|     if (trips.empty()) | ||||
|     // rotate result such that roundtrip starts at node with index 0
 | ||||
|     // thist first if covers scenarios: !fixed_end || fixed_start || (fixed_start && fixed_end)
 | ||||
|     if (!fixed_end || fixed_start) | ||||
|     { | ||||
|         return Error("NoTrips", "Cannot find trips", json_result); | ||||
|         auto desired_start_index = std::find(std::begin(trip), std::end(trip), 0); | ||||
|         BOOST_ASSERT(desired_start_index != std::end(trip)); | ||||
|         std::rotate(std::begin(trip), desired_start_index, std::end(trip)); | ||||
|     } | ||||
|     else if (fixed_end && !fixed_start && parameters.roundtrip) | ||||
|     { | ||||
|         auto desired_start_index = std::find(std::begin(trip), std::end(trip), destination_id); | ||||
|         BOOST_ASSERT(desired_start_index != std::end(trip)); | ||||
|         std::rotate(std::begin(trip), desired_start_index, std::end(trip)); | ||||
|     } | ||||
| 
 | ||||
|     // compute all round trip routes
 | ||||
|     std::vector<InternalRouteResult> routes; | ||||
|     routes.reserve(trips.size()); | ||||
|     for (const auto &trip : trips) | ||||
|     { | ||||
|         routes.push_back(ComputeRoute(facade, snapped_phantoms, trip)); | ||||
|     } | ||||
|     // get the route when visiting all destinations in optimized order
 | ||||
|     InternalRouteResult route = ComputeRoute(facade, snapped_phantoms, trip, parameters.roundtrip); | ||||
| 
 | ||||
|     // get api response
 | ||||
|     const std::vector<std::vector<NodeID>> trips = {trip}; | ||||
|     const std::vector<InternalRouteResult> routes = {route}; | ||||
|     api::TripAPI trip_api{*facade, parameters}; | ||||
|     trip_api.MakeResponse(trips, routes, snapped_phantoms, json_result); | ||||
| 
 | ||||
|  | ||||
| @ -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