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 |       - 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 | ||||||
|  | |||||||
							
								
								
									
										54
									
								
								docs/http.md
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								docs/http.md
									
									
									
									
									
								
							| @ -175,7 +175,7 @@ In addition to the [general options](#general-options) the following options are | |||||||
| |annotations |`true`, `false` (default)                    |Returns additional metadata for each coordinate along the route geometry.      | | |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. | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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'); | ||||||
|  | |||||||
| @ -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  | | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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(); } | ||||||
| }; | }; | ||||||
| } | } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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_) | ||||||
|  | |||||||
| @ -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
 | ||||||
|  | |||||||
| @ -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
 | ||||||
|  | |||||||
| @ -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; | ||||||
| }; | }; | ||||||
| } | } | ||||||
| } | } | ||||||
|  | |||||||
| @ -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_); } | ||||||
|  | |||||||
| @ -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 | ||||||
| { | { | ||||||
|  | |||||||
| @ -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; | ||||||
|  | |||||||
| @ -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 ¶meters, |                                  const api::TripParameters ¶meters, | ||||||
|                                  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); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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 ¶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() | BOOST_AUTO_TEST_SUITE_END() | ||||||
|  | |||||||
| @ -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() | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user